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
Eseguire script pre e post esecuzione di un workflow di GitHub
Gestire eccezioni nei plugin di Semantic Kernel in ASP.NET Core Web API
Sviluppare un'interfaccia utente in React con Tailwind CSS e Preline UI
Gestire gli accessi con Token su Azure Container Registry
Supporto ai tipi DateOnly e TimeOnly in Entity Framework Core
Aggiornare a .NET 9 su Azure App Service
Garantire la provenienza e l'integrità degli artefatti prodotti su GitHub
Gestire la cancellazione di una richiesta in streaming da Blazor
Ordinare randomicamente una lista in C#
Paginare i risultati con QuickGrid in Blazor
Creare una libreria CSS universale: i bottoni
Eseguire operazioni sui blob con Azure Storage Actions
I più letti di oggi
- Simulare Azure Cosmos DB in locale con Docker
- Utilizzare il metodo Index di LINQ per scorrere una lista sapendo anche l'indice dell'elemento
- ecco tutte le novità pubblicate sui nostri siti questa settimana: https://aspit.co/wkly buon week-end!
- .NET Conference Italia 2024 - Milano
- .NET Conference Italia 2023 - Milano e Online