Articoli

Validazione remota di una Form

da

Quando si parla di validazione, nella maggioranza dei casi, ci riferiamo a uno o più campi specifici della form che vanno validati sulla base di regole formali, come obbligatorietà e lunghezza massima di caratteri. Questa validazione può essere fatta sia in locale che lato server, con delle regole impostate con le DataAnnotation oppure la FluentValidation.

Immaginiamo invece il caso in cui in una classica anagrafica, con nome, cognome, codice fiscale e data nascita in cui vogliamo che il codice fiscale, oltre ad essere valido formalmente, sia anche univoco nel nostro database. In questo caso dobbiamo richiedere al server di verificare che il codice inserito non sia già presente.

Un’altra ragione per invocare una validazione server-side potrebbe essere la verifica che l’anagrafica inserita non sia già presente ma bannata per qualche ragione, quindi non possiamo permettere l’iscrizione. Più in generale vogliamo affrontare il problema di una validazione business mentre compiliamo un form.

Partiamo definendo il nostro modello

public class FormModel
{
    [Required]
    public string Nome { get; set; } = string.Empty;

    [Required]
    public string Cognome { get; set; } = string.Empty;

    [Required]
    public string CodiceFiscale { get; set; } = string.Empty;

    [Required]
    public DateOnly DataNascita { get; set; }

    public FormModel()
    {
        DataNascita = DateOnly.FromDateTime(DateTime.Today);
    }
}

Per semplificare l’esempio usiamo solo una regola per ogni campo, ma come sappiamo sono possibili più regole.

Per gestire in maniera completa la validazione nella form abbiamo bisogno del Modello, dell’EditContext e del ValidationMessageStore. Iniziamo con la modalità più semplice per inizializzare la form in modo da affrontare i problemi in successione.

@page "/"
@inject ApiService service

<PageTitle>Index</PageTitle>
<div class="container-fluid">
    <EditForm @ref="@refEditForm" EditContext="@editContext" OnValidSubmit="@OnValidSubmit">
        <DataAnnotationsValidator />
        <ValidationSummary />

        <div class="row mb-3">
            <label class="col-3 col-form-label">Nome</label>
            <div class="col-9">
                <InputText class="form-control" @bind-Value="formModel.Nome" />
            </div>

        </div>
        <div class="row mb-3">
            <label class="col-3 col-form-label">Cognome</label>
            <div class="col-9">
                <InputText class="form-control" @bind-Value="formModel.Cognome" />
            </div>
        </div>
        <div class="row mb-3">
            <label class="col-3 col-form-label">Codice Fiscale</label>
            <div class="col-9">
                <InputText class="form-control" @bind-Value="formModel.CodiceFiscale" />
            </div>
        </div>
        <div class="row mb-3">
            <label class="col-3 col-form-label">Data Nascita</label>
            <div class="col-9">
                <InputDate class="form-control" @bind-Value="formModel.DataNascita" />
            </div>
        </div>
        <button type="submit" class="btn btn-primary mb-3">Salva</button>
    </EditForm>
</div>


@code {
    private FormModel formModel = new();
    private EditForm? refEditForm;
    private EditContext? editContext;
    private ValidationMessageStore? validationMessageStore;

    protected override void OnInitialized()
    {
        editContext = new(formModel);
        validationMessageStore = new(editContext);
    }

    private void OnValidSubmit()
    {
        var errors = service.Post(formModel);
        if(errors != null)
        {
            foreach(var err in errors)
            {
                validationMessageStore!.Add(editContext!.Field(err.Key), err.Value);
            }
        }
    }
}

Come dicevo, il server può validare i dati e informarci sui problemi del singolo campo oppure di un errore generico (sempre dovuto alla form e non ad errori di server tecnici che non sono trattati in questo articolo). Tali errori di validazione sono facilmente gestibili se li riceviamo nel formato:

Dictionary<string, string[]>

La chiave del dizionario, di tipo string, contiene il nome del campo, mente il valore, di tipo string[], contiene l’elenco di messaggi di validazione dello specifico campo. Tale formato ci può essere inviato dal server sia tramite un nostro oggetto DTO che contiene la proprietà detta sopra con nome “Errors”, oppure usare lo standard ProblemDetais che contiene anche la proprietà “Errors” dello stesso tipo.

Sempre per semplificare creiamo un servizio che invia la form al server e, nel caso di validazioni non superate, ricevere gli errori:

public class ApiService
{
    public Dictionary<string, string[]>? Post(FormModel model)
    {
        return null;
    }
}

Possiamo già testare la nostra form e vedere che la validazione risponde solo alle regole usate in DataAnnotation. Questo significa che in caso di errori di validazione di quel tipo, il metodo OnValidSubmit non viene invocato, mostrando tutti gli errori all’inizio della form oppure sotto ogni campo.

Notiamo che quando inseriamo un valore, ad esempio nel campo nome, perso il focus l’errore di validazione viene eliminato in automatico (sempre che non ne commettiamo un altro). Simulando un codice fiscale già in uso nel nostro servizio, aggiungiamo manualmente l’errore ricevuto server side:

public class ApiService
{
    public Dictionary<string, string[]>? Post(FormModel model)
    {
        return model.CodiceFiscale == "aaa" ?
            new() { { "CodiceFiscale", new[] { "Codice Fiscale già in uso" } } } :
            null;
    }
}

Testiamo il servizio, ed ecco il risultato:

Il tutto funziona nella UI. L’EditContext sta gestendo l’errore proveniente dal server e che abbiamo inserito in validationMessageStore. Ma cosa succede se andiamo a modificare il valore del codice fiscale?
Questa volta abbandonato il focus, il messaggio di errore non viene eliminato; inoltre, essendo presenti errori di validazione, il mentodo OnValidSubmit non viene invocato. Ci troviamo in una form non più utilizzabile. La validazione automatica non è in grado di gestire errori esterni diversi dai conosciuti (DataAnnotation) come in quest’ultimo caso. Per questo motivo dobbiamo farlo noi. Cioè dobbiamo eliminare ogni tipo di errore dal campo che abbiamo appena modificato.

Per fare questo ci registriamo ad un evento di EditContext: “OnFieldChanged” che come argomento ci riporta il campo che abbiamo appena modificato.

protected override void OnInitialized()
{
    editContext = new(formModel);
    validationMessageStore = new(editContext);
    <strong>editContext.OnFieldChanged += OnFieldChanged;</strong>
}

private void OnFieldChanged(object? sender, FieldChangedEventArgs args)
{
    validationMessageStore!.Clear(args.FieldIdentifier);
}

Primo problema risolto. Ora passiamo al caso in cui l’errore è generico e non riguarda un campo specifico.

Modifichiamo il servizio per generare l’errore:

public class ApiService
{
    public Dictionary<string, string[]>? Post(FormModel model)
    {
        return model.DataNascita == DateOnly.FromDateTime(DateTime.Today) ?
            new() { { "Form", new[] { "Codice Fiscale e Data Nascita non sono coerenti" } } } :
            null;
    }
}

In questo caso abbiamo prodotto un errore generico (anche se nell’esempio valuto un singolo campo della form per farlo generare), ma ipotizziamo che il server abbia decodificato il codice fiscale e abbia valutato l’incoerenza con la data di nascita. Quindi l’errore non è specificatamente della data di nascita e/o del codice fiscale.

Non essendo l’errore imputabile a nessun campo specifico, usiamo come “nostra” regola quella di inserire tali errori nella KEY “Form”. Questo comporta che l’errore sarà presente nella lista errori, ma non “accenderà” nessun campo, non essendocene alcuno col nome “Form”. Simuliamo ad esempio un errore se usiamo come data di nascita la data odierna (per altro auto generata dal modello). Ci ritroviamo così in questa situazione:

Il caso è simile al precedente. Anche qui, modificando i campi, l’errore persiste e non possiamo procedere con il submit della form.
La soluzione è quella di eliminare il messaggio di errore del campo “virtuale” Form in qualunque modifica di altri campi usando la registrazione precedente:

private void OnFieldChanged(object? sender, FieldChangedEventArgs args)
{
    validationMessageStore!.Clear(args.FieldIdentifier);
    validationMessageStore!.Clear(editContext!.Field("Form"));
}

Concludendo, con queste semplici righe è un paio di convenzioni, abbiamo risolto i problemi della form in Blazor.

Un saluto a tutti i lettori.

Scopri di più da Blazor Developer Italiani

Abbonati ora per continuare a leggere e avere accesso all'archivio completo.

Continue reading