Parlando di Blazor, abbiamo introdotto più volte le potenzialità del databinding e mostrato come, nel caso dei controlli di input, l'attributo @bind permetta non solo di leggere il contenuto di una variabile, ma anche di aggiornarlo in automatico dopo l'interazione dell'utente. Questa tecnica è nota come databinding bidirezionale, ed elimina molte delle complessità nella gestione dello stato di pagina.
Per esempio, una checkbox può essere collegata a una proprietà di tipo bool con il semplicissimo codice in basso, e vedremo che, in maniera del tutto automatica, il contenuto del testo "MyValue is ..." si modificherà in sincrono con lo stato della checkbox stessa.
<p><label> <input type="checkbox" @bind="this.MyValue" /> Click me! </label></p> <p>MyValue is: @MyValue</p> @code { public bool MyValue { get; set; } }
Spesso però, ci troviamo nella necessità di eseguire del codice, magari asincrono, al click della checkbox stessa. Per esempio vorremo salvare la selezione dell'utente sul database, e purtroppo l'implementazione è meno banale di quanto sembri.
Il problema dell'evento onclick
In prima approssimazione potremmo pensare di usare l'evento @onclick:
<p> <label> <input type="checkbox" @bind="this.MyValue" @onclick="ClickEventHandlerAsync" /> Click me! </label></p> <p>MyValue is: @MyValue</p> @code { public bool MyValue { get; set; } public async Task ClickEventHandlerAsync() { await js.InvokeVoidAsync("console.log", $"MyValue was {this.MyValue}"); } }
Se provassimo a eseguire il codice in alto, tuttavia, ci renderemmo conto che presenta un fastidioso problema: il metodo ClickEventHandlerAsync viene infatti eseguito prima che il databinding abbia luogo, e pertanto il valore che leggiamo su MyValue è ancora quello precedente al click.
Inoltre, questa soluzione può essere fonte di pericolosi bug, perché il codice asincrono viene processato in parallelo al binding stesso. Di conseguenza, diverse linee di codice in quel metodo potrebbero leggere diversi valori di MyValue. Ce ne possiamo facilmente accorgere loggando il valore su console più di una volta: vedremo che non tutte le righe scriveranno la stessa cosa!
public async Task ClickEventHandlerAsync() { await js.InvokeVoidAsync("console.log", $"1 - MyValue was {this.MyValue}"); await js.InvokeVoidAsync("console.log", $"2 - MyValue was {this.MyValue}"); }
La soluzione più affidabile
Per risolvere il problema, dobbiamo invece sfruttare l'evento @onchange, che si scatena dopo il click, e rinunciare al binding bidirezionale. Sia @bind che @onchange, infatti, sfruttano il medesimo evento HTML, sollevando un errore di compilazione se proviamo a usarli contemporaneamente.
Riscriviamo allora il codice in pagina come segue:
<p> <label> <input type="checkbox" checked="@this.MyValue" @onchange="this.ClickEventHandlerAsync" /> Click me! </label></p> <p>MyValue is: @MyValue</p> @code { public bool MyValue { get; set; } public async Task ClickEventHandlerAsync(ChangeEventArgs eventArgs) { // recuperiamo il valore proveniente da HTML e lo assegniamo manualmente this.MyValue = (bool)eventArgs.Value; // a questo punto siamo sicuri che MyValue abbia il valore corretto await js.InvokeVoidAsync("console.log", $"MyValue was {this.MyValue}"); } }
Innanzi tutto la checkbox ora usa il binding in sola lettura, presentando sì il suo stato checked in base al valore di MyValue, ma non è più in grado di modificare automaticamente la variabile. Quest'ultima operazione è contenuta all'interno di ClickEventHandlerAsync, che ora accetta un parametro di tipo ChangeEventArgs che a sua volta contiene il nuovo valore per MyValue.
Si tratta sicuramente di un codice meno conciso dell'esempio precedente, ma che garantisce che il valore di MyValue sia coerente con lo stato della checkbox e non soffra dei problemi che abbiamo avuto modo di introdurre.
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Persistere la ChatHistory di Semantic Kernel in ASP.NET Core Web API per GPT
Creare alias per tipi generici e tuple in C#
Hosting di componenti WebAssembly in un'applicazione Blazor static
Generare velocemente pagine CRUD in Blazor con QuickGrid
Usare un KeyedService di default in ASP.NET Core 8
Rinnovare il token di una GitHub App durante l'esecuzione di un workflow
Inference di dati strutturati da testo con Semantic Kernel e ASP.NET Core Web API
Garantire la provenienza e l'integrità degli artefatti prodotti su GitHub
Evitare (o ridurre) il repo-jacking sulle GitHub Actions
Creare una libreria CSS universale: i bottoni
Migliorare la scalabilità delle Azure Function con il Flex Consumption