Jeśli trafiłeś tu tylko po to, żeby dowiedzieć się, jak walidować złożony obiekt, zobacz końcówkę artykułu.

Kiedyś napisałem co nieco o walidacji w .NetCore MVC. Możesz te artykuły przeczytać tu i tu. Powinieneś je przeczytać, ponieważ mówią również o podstawach walidacji jako takiej (w tym o data annotation validation, którym będziemy się tu posługiwać).

Wchodząc w świat Blazor, bardzo szybko będziesz chciał tworzyć formularze i je walidować. Na szczęście w Blazor jest to chyba jeszcze prostsze niż w Razor.

Pierwszy formularz

Blazor ma wbudowany komponent, który nazywa się EditForm. Jak można się domyślić, jest to po prostu formularz. Ma on kilka ciekawych możliwości, w tym artykule skupimy się jednak tylko na walidacji.

Na początek stwórzmy bardzo prosty model – Customer. Tę klasę utworzymy oczywiście w osobnym pliku Customer.cs.

public class Customer
{
	[Required]
	[StringLength(50, MinimumLength = 5)]
	public string Name { get; set; }
}

Jest klasa z jednym polem i dodanymi adnotacjami:

  • Pole jest wymagane
  • Musi mieć długość minimum 5 znaków i maksimum 50.

Teraz utwórzmy w pliku .razor stronę z formularzem:

@page "/"
@using Models

<EditForm Model="Customer" OnValidSubmit="SubmitForm">
    <div>
        <label for="name">Imię i nazwisko:</label>
        <InputText id="name" @bind-Value="Customer.Name" />
    </div>
    <div>
        <button type="submit">Zapisz</button>
    </div>    
</EditForm>

@code
{
    Customer Customer = new Customer();

    async Task SubmitForm()
	{

	}
}

Spójrz, co mamy w sekcji code. Tworzymy nowy obiekt dla naszego modelu. To jest ważne, ponieważ ten obiekt powiążesz z formularzem.

Teraz spójrz wyżej na komponent EditForm. To jest właściwie najbardziej podstawowa konstrukcja formularza. Mamy właściwość Model, która określa OBIEKT powiązany (zbindowany) z formularzem. Pamiętaj, że to jest obiekt utworzony w sekcji code, a nie nazwa klasy.

Dalej mamy zdarzenie OnValidSubmit. Przypisujemy tutaj metodę, która wykona się po wysłaniu formularza, gdy będzie on prawidłowy. Z tego wynika, że ta metoda nie zostanie wykonana, jeśli formularz będzie zawierał błędy (są inne zdarzenia, które do tego służą).

Dalej w formularzu masz komponent InputText, który określa powiązanie z odpowiednim polem modelu – za pomocą @bind-value – czyli typowy binding z Blazor.

No i na koniec mamy guzik do wysłania formularza – pamiętaj, że w formularzu możesz mieć TYLKO JEDEN guzik typu submit.

Dodajemy walidację do strony

Przede wszystkim trzeba powiedzieć formularzowi, żeby walidował model za pomocą DataAnnotations (można to zrobić inaczej, ale w tym artykule skupiamy się na prostych podstawach). W tym celu trzeba mu dorzucić komponent DataAnnotationsValidator:

<EditForm Model="Customer" OnValidSubmit="SubmitForm">
    <DataAnnotationsValidator/>
    <div>
        <label for="name">Imię i nazwisko:</label>
        <InputText id="name" @bind-Value="Customer.Name" />
    </div>
    <div>
        <button type="submit">Zapisz</button>
    </div>    
</EditForm>

Właściwie to załatwia sprawę, formularz może być już walidowany. Sprawdź to. Jeśli spróbujesz wysłać pusty formularz, wymagane pole zaświeci się na czerwono.

Komunikaty błędów

Pewnie chciałbyś uzyskać jakiś komunikat błędu? Można to zrobić na kilka sposobów. Najprościej – dodaj komponent ValidationSummary – czyli podsumowanie walidacji.

<EditForm Model="Customer" OnValidSubmit="SubmitForm">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label for="name">Imię i nazwisko:</label>
        <InputText id="name" @bind-Value="Customer.Name" />
    </div>
    <div>
        <button type="submit">Zapisz</button>
    </div>
</EditForm>

Możesz go dodać w dowolnym miejscu formularza – tutaj pojawią się po prostu komunikaty o błędach. Uruchom teraz i zobacz, co się stanie, gdy podasz nieprawidłowe dane:

Widzimy opis błędu i podświetlone konkretne pole. Przy czym zwróć uwagę, że opis błędu wskazuje konkretnie na pole o nazwie „Name” – to zostało wzięte z modelu. Można to zmienić. Wszystkie rodzaje inputów (a jest ich kilka) w EditForm mają właściwość DisplayName. Dodaj ją:

<InputText id="name" @bind-Value="Customer.Name" DisplayName="imię i nazwisko"/>

To jest po prostu przyjazna nazwa pola dla walidacji. Niestety w mojej wersji Blazor (wrzesień 2021) ta właściwość nie chce działać. Ale nic to. I tak w prawdziwym świecie posłużymy się odpowiednią adnotacją w modelu:

public class Customer
{
	[Required(ErrorMessage = "Musisz wypełnić imię i nazwisko")]
	[StringLength(50, MinimumLength = 5, ErrorMessage = "Imię i nazwisko musi być dłuższe niż 5 znaków i krótsze niż 50")]
	public string Name { get; set; }
}

Pisałem już o tych mechanizmach w artykule o własnej walidacji w .NetCore, więc nie będę się tutaj powtarzał.

Wymagany checkbox

W artykule o własnej walidacji w .NetCore pisałem też o tym, jak zrobić wymagany checkbox. Np do akceptacji regulaminu. Wtedy trzeba było się nieźle nakombinować. Dzisiaj jest dużo prościej i zdecydowanie bardziej przyjaźnie. Spójrz na model:

public class Customer
{
	[Required(ErrorMessage = "Musisz wypełnić imię i nazwisko")]
	[StringLength(50, MinimumLength = 5, ErrorMessage = "Imię i nazwisko musi być dłuższe niż 5 znaków i krótsze niż 50")]
	public string Name { get; set; }

	[Required]
	[Range(typeof(bool), "true", "true", ErrorMessage = "Musisz zaakceptować plitykę prywatności")]
	public bool PrivacyAgreed { get; set; }
}

I pole w formularzu:

<EditForm Model="Customer" OnValidSubmit="SubmitForm">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label for="name">Imię i nazwisko:</label>
        <InputText id="name" @bind-Value="Customer.Name"/>
    </div>
    <div>
        <InputCheckbox id="agreement" @bind-Value="Customer.PrivacyAgreed" />
        <label for="agreement">Zgoda na politykę prywatności</label>
    </div>
    <div>
        <button type="submit">Zapisz</button>
    </div>
</EditForm>

I już. Wszystko załatwione adnotacjami.

Nie wiem jak Ty, ale ja nie lubię widzieć całej listy rzeczy, które zostały źle wprowadzone. Lubię, jak każdy błąd jest wyświetlony przy konkretnym polu. Można to zrobić bardzo prosto.

Błędy przy konkretnych polach

W tym momencie możesz pozbyć się komponentu ValidationSummary. To on właśnie pokazuje pełną listę błędów. Pamiętaj jednak, żeby zostawić DataAnnotasionsValidator, który odpowiada za walidację.

Teraz do każdego walidowanego pola możesz dodać komponent ValidationMessage:

<EditForm Model="Customer" OnValidSubmit="SubmitForm">
    <DataAnnotationsValidator />
    <div>
        <label for="name">Imię i nazwisko:</label>
        <InputText id="name" @bind-Value="Customer.Name"/>
        <ValidationMessage For="() => Customer.Name" />
    </div>
    <div>
        <InputCheckbox id="agreement" @bind-Value="Customer.PrivacyAgreed" />
        <label for="agreement">Zgoda na politykę prywatności</label>
        <ValidationMessage For="() => Customer.PrivacyAgreed"/>
    </div>
    <div>
        <button type="submit">Zapisz</button>
    </div>
</EditForm>

Oczywiście te komponenty możesz umieścić gdziekolwiek w formularzu. Za pomocą właściwości For określasz, do którego pola się odnoszą. Zwróć uwagę, że przekazujesz tam lambdę, a nie nazwę pola

Wszystko fajnie, tylko że brzydko. A co, jeśli chcielibyśmy trochę popracować nad wyglądem komunikatów? Można to zrobić. W standardowym projekcie znajduje się plik site.css (w katalogu wwwroot/css). Jak sobie popatrzysz do środka, zobaczysz taki styl:

.validation-message {
    color: red;
}

To właśnie odpowiada za wygląd komunikatów. Możemy to zmienić, np:

.validation-message {
    color: #8a0000;
    font-size: smaller;
    font-weight: bold;
    margin-bottom: 20px;
}

Teraz, to wygląda tak:

Walidacja obiektu złożonego

Co ma każdy klient? Każdy klient ma swój adres. Stwórzmy teraz bardzo prosty model adresu:

public class Address
{
	[Required(ErrorMessage = "Musisz podać nazwę ulicy")]
	public string StreetName { get; set; }
	[Required(ErrorMessage = "Musisz podać numer ulicy")]
	public string StreetNumber { get; set; }
	[Required(ErrorMessage = "Musisz podać miasto")]
	public string City { get; set; }
}

I dodajmy go do modelu klienta, a także do formularza:

public class Customer
{
	[Required(ErrorMessage = "Musisz wypełnić imię i nazwisko")]
	[StringLength(50, MinimumLength = 5, ErrorMessage = "Imię i nazwisko musi być dłuższe niż 5 znaków i krótsze niż 50")]
	public string Name { get; set; }

	[Required]
	[Range(typeof(bool), "true", "true", ErrorMessage = "Musisz zaakceptować plitykę prywatności")]
	public bool PrivacyAgreed { get; set; }

    public Address Address { get; set; } = new Address();
}

Formularz:

<EditForm Model="Customer" OnValidSubmit="SubmitForm">
    <DataAnnotationsValidator />
    <div>
        <label for="name">Imię i nazwisko:</label>
        <InputText id="name" @bind-Value="Customer.Name" />
        <ValidationMessage For="() => Customer.Name" />
    </div>
    <div>
        <InputCheckbox id="agreement" @bind-Value="Customer.PrivacyAgreed" />
        <label for="agreement">Zgoda na politykę prywatności</label>
        <ValidationMessage For="() => Customer.PrivacyAgreed" />
    </div>

    <div>
        <label for="street">Ulica:</label>
        <InputText id="street" @bind-Value="Customer.Address.StreetName" />
        <ValidationMessage For="() => Customer.Address.StreetName" />
    </div>

    <div>
        <label for="street_no">Nr ulicy:</label>
        <InputText id="street_no" @bind-Value="Customer.Address.StreetNumber" />
        <ValidationMessage For="() => Customer.Address.StreetNumber" />
    </div>

    <div>
        <label for="city">Miasto:</label>
        <InputText id="city" @bind-Value="Customer.Address.City" />
        <ValidationMessage For="() => Customer.Address.City" />
    </div>

    <div>
        <button type="submit">Zapisz</button>
    </div>
</EditForm>

I co? I nie działa…

Okazuje się, że Blazor ma pewne problemy z takimi polami, chociaż być może jest to celowe działanie. Jeszcze dosłownie niedawno trzeba było tworzyć obejścia. Jednak teraz mamy to PRAWIE w standardzie:

  1. Pobierz NuGet: Microsoft.AspNetCore.Components.DataAnnotations.Validation. UWAGA! Na wrzesień 2021 ten pakiet nie jest jeszcze oficjalnie wydany. Jest to prerelease. A więc możliwe, że będziesz musiał zaznaczyć opcję Include Prerelase w NuGet managerze:
  1. W swoim modelu Customer oznacz pole Address atrybutem: ValidateComplexType. Pamiętaj, że jest to składnik pobranego właśnie NuGeta. Jeśli więc masz modele w innym projekcie, musisz tam też pobrać ten pakiet.
  2. W razor zmień DataAnnotationsValidator na ObjectGraphDataAnnotationsValidator

I to na tyle. Teraz wszystko już działa. ObjectGraphAnnotationsValidator sprawdzi walidacje wszystkich zwykłych pól, jak i tych „kompleksowych”.

Wbudowane kontrolki

Powiem jeszcze na koniec o kontrolkach formularzy wbudowanych w Blazor. Oczywiście możemy tworzyć własne jeśli będzie taka potrzeba (czasami jest), jednak do dyspozycji na starcie mamy:

  • InputText
  • InputCheckbox
  • InputDate
  • InputFile
  • InputNumber
  • InputRadio
  • InputRadioGroup
  • InputSelect
  • InputTextArea

To tyle, jeśli chodzi o podstawy walidacji w Blazor. Jeśli masz jakieś pytania lub znalazłeś błąd w artykule, podziel się w komentarzu.

Podziel się artykułem na: