Pokażę Ci jak zrobić własną walidację na bardzo użytecznym przykładzie. Zrobimy mechanizm sprawdzenia, czy użytkownik wyraził zgodę na regulamin. Jeśli nie wiesz, jak działa walidacja w .NetCore, sprawdź najpierw ten artykuł.
Stworzenie własnej walidacji składa się z dwóch kroków.
- utworzenie własnego atrybutu
- dodanie sprawdzenia po stronie JavaScript.
Zasadniczo jest to dość proste, ale trzeba pamiętać o pewnej rzeczy… o tym później
Walidacja po stronie klienta
Required nie wystarczy
W .NET można tworzyć własne atrybuty. To wiadomo. Atrybut to po prostu klasa, która dziedziczy po specyficznej klasie i zawiera… konkretne atrybuty 🙂
Z jakiegoś powodu w .NetCore nie ma domyślnie możliwości sprawdzenia, czy checkbox został zaznaczony. Wydawać by się mogło, że wystarczy:
class RegisterUserViewModel
{
[Required]
public bool TermsAndConditions { get; set; }
}
Widziałem też takie przykłady:
class RegisterUserViewModel
{
[Range(typeof(bool), "true", "true")]
public bool TermsAndConditions { get; set; }
}
niestety z checkboxem i jego wartością jest nieco inaczej. I powinien zostać do tego stworzony nowy atrybut. Rozwiązanie, które tutaj podaję zaproponowałem do Microsoftu. Być może w kolejnych wersjach wdrożą coś analogicznego.
Dopisane: W Blazor można walidować checkbox za pomocą atrybutu Required
Tworzenie własnego atrybutu
Zatem stwórzmy własny atrybut walidacyjny. Takie atrybuty powinny dziedziczyć po klasie ValidationAttribute
. Jest to klasa abstrakcyjna, która zawiera pewną wspólną logikę dla wszystkich walidacji.
ValidationAttribute
zapewnia walidację po stronie serwera. Jeśli chcesz mieć zapewnioną dodatkowo walidację po stronie klienta, musisz zaimplementować interfejs IClientModelValidator
.
IClientModelValidator
Tutaj na chwilę się zatrzymamy. Walidacja po stronie klienta zawsze odbywa się za pomocą JavaScript (lub podobnych). .NetCore to znacznie ułatwia, gdyż wprowadza własny prosty mechanizm. Spójrz na ten prosty formularz:
<form asp-action="Register" method="Post">
<div class="form-group">
<label for="email">Podaj swój email:</label>
<input class="form-control" type="email" asp-for="UserName" />
<span asp-validation-for="UserName" class="text-danger"></span>
</div>
</form>
Widzisz tutaj pomocnicze tagi: asp-for
i asp-validation-for
. W wynikowym HTMLu będzie to wyglądało mniej-więcej tak:
<div class="form-group">
<label for="email">Podaj swój email:</label>
<input class="form-control" type="email" data-val="true" data-val-email="The Email field is not a valid e-mail address." data-val-required="The UserName field is required." id="UserName" name="UserName" value="">
<span class="text-danger field-validation-valid" data-valmsg-for="UserName" data-valmsg-replace="true"></span>
</div>
Jak widzisz, tagi pomocnicze trochę tutaj nagrzebały w kodzie. Nagrzebały pod kątem Microsoftowej biblioteki jQuery Unobtrusive Validation. Ta biblioteka, można powiedzieć, „szuka” atrybutów data-val
(skrót od „data validation”), których wartość jest ustawiona na true
. Oznacza to, że takie pole podlega walidacji. Następnie tworzone są odpowiednie komunikaty, gdy walidacja się nie powiedzie:
- data-val-email – komunikat dla atrybutu [EmailAddress]
- data-val-required – komunikat dla atrybutu [Required]
Te komunikaty są później umieszczane w elemencie, który ma data-valmsg-for
i data-valmsg-replace
(true
).
To się dzieje automatycznie – Microsoft porobił takie mechanizmy. Tutaj wchodzi interfejs IClientModelValidator
. Zajmijmy się najpierw nim:
Implementacja IClientModelValidator
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class CheckBoxCheckedAttribute : ValidationAttribute, IClientModelValidator
{
public void AddValidation(ClientModelValidationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-chboxreq", "Nie zaznaczyłeś checkboxa!");
}
private static void MergeAttribute(IDictionary<string, string> attributes, string key, string value)
{
if (!attributes.ContainsKey(key))
{
attributes.Add(key, value);
}
}
}
context.Attributes
to słownik, który zawiera atrybuty dodane już do pola
Użycie atrybutu
Utworzyliśmy klasę CheckBoxCheckedAttribute
, implementując interfejs IClientModelValidator
.
Teraz tak. Tag pomocniczy asp-for
sprawdza wszystkie walidacje na danym polu, czyli jeśli mamy model:
class RegisterUserViewModel
{
[CheckBoxChecked]
public bool TermsAndConditions { get; set; }
}
wtedy asp-for
weźmie wszystkie klasy atrybutów dla danego pola, które implementują interfejs IClientModelValidator
i uruchomi metodę AddValidation
.
To, co „zwróci” ta metoda, będzie dopisane do pola . Jak widzisz, zadaniem metody AddValidation
jest właściwie tylko odpowiednie dodanie atrybutów do pola input
, a więc:
- data-val usatwione na true
- data-val-chboxreq – którego wartością jest odpowiedni komunikat. Czyli teraz dopiszemy checkboxa w formularzu:
<form asp-action="Register" method="Post">
<div class="form-group">
<div class="form-check">
<input type="checkbox" class="form-check-input" asp-for="TermsAndConditions" />
<label class="form-check-label" asp-for="TermsAndConditions">Akceptuję warunki regulaminu(acceptLabel)</label>
</div>
<span asp-validation-for="TermsAndConditions" class="text-danger"></span>
</div>
</form>
A ostatecznie pole input będzie wyglądało podobnie do tego:
<input type="checkbox" class="form-check-input" data-val="true" data-val-chboxreq="Nie zaznaczyłeś checkboxa!" />
Zauważ, że klasa CheckBoxCheckedAttribute
dodała w metodzie AddValidation
te dwa atrybuty (data-val i data-val-chboxreq), które są wymagane do poprawnego działania walidacji po stronie klienta.
Oczywiście nie musisz pisać nazywać tego data-val-chboxreq
. Ważne, żeby było data-val-
i w miarę unikalna, jednoznaczna końcówka. Równie dobrze mógłbyś napisać: data-val-checkbox-zaznaczony
i sprawdzać później wartość tego atrybutu.
Czyli podsumowując tę część:
Tag asp-for
powoduje wywołanie metody AddValidation
z interfejsu IClientModelValidator
, która jest odpowiedzialna za dodanie atrybutów walidacyjnych do pola . Oczywiście nic nie stoi na przeszkodzie, żebyś dodał tam jeszcze inne atrybuty. Pamiętaj, że musisz atrybut implementujący IClientModelValidator
dodać do odpowiedniego pola w swoim modelu (viewmodelu).
OK, skoro już wiesz do czego służy IClientModelValidator
, to teraz polecimy dalej. Pierwsza część walidacji jest zrobiona. Teraz druga. JavaScript.
Walidacja w JavaScript
Jak już mówiłem, Microsoft ma tą swoją bibliotekę jQuery Unobtrusive Validation, która działa razem z jQuery Validation.
Ten fragment kodu możesz dodać albo tylko na stronie z formularzem, albo lepiej – w widoku _ValidationScriptsPartial. Wtedy będzie dostępny zawsze, gdy dodajesz jakąś walidację:
<script>
$.validator.addMethod("chboxreq", function (value, element, param) {
if (element.type != "checkbox")
return false;
return element.checked;
});
$.validator.unobtrusive.adapters.addBool("chboxreq");
</script>
- najpierw dodajemy metodę do walidatora jQuery.
- metoda ma zostać uruchomiona, gdy trafi na atrybut
data-val-chboxreq
. Zauważ, że w parametrze podajemy tylko tę ostatnią część –chboxreq
. - funkcja przyjmuje 3 parametry – wartość (atrybut value elementu HTML), element – czyli element HTML i param – dodatkowy parametr.
Jak widzisz funkcja walidująca jest bardzo prosta – najpierw jest sprawdzenie, czy element jest checkboxem, potem funkcja sprawdza, czy checkbox jest zaznaczony.
Aby to wszystko zadziałało, trzeba jeszcze dodać ten „adapter”, używając $.validator.unobtrusive.adapters
Nie będę tutaj opisywał walidatorów w jQuery, bo:
- Mógłbym zaciemnić obraz
- Nie znam się na nich
- Możesz sam wyszukać w dokumentacjach, jeśli będziesz chciał robić jakieś dziwne rzeczy.
I teraz pewna uwaga, o której pisałem na początku. Ten cały kod nie chciał mi kiedyś zadziałać i męczyłem się chyba 2 godziny zanim wpadłem dlaczego (oczywiście nie byłbym sobą, gdybym nie zgłosił tego do Microsoftu :)). Ten kod nie zadziała z poziomu TypeScript. Nie wiem, czemu. Po prostu MUSI być bezpośrednio w JavaScript.
Walidacja po stronie serwera
Aby wszystko zadziałało poprawnie, musimy dodać jeszcze walidację po stronie serwera. Zauważ, że walidacja kliencka została zrobiona w JavaScript. Walidacja po stronie serwera odbywa się już bezpośrednio w klasie CheckBoxCheckedAttribute
. Wystarczy przesłonić metodę IsValid
:
public override bool IsValid(object value)
{
return (value is bool && (bool)value);
}
To tyle.
Komunikaty o błędach
Teraz mała uwaga na koniec. Klasa ValidationAttribute
zawiera już pewne mechanizmy, które pomagają.
Jak już wiesz, każdy atrybut walidacyjny z .NET ma pewne właściwości: ErrorMessage
, ErrorMessageResourceName
, ErrorMessageResourceType
… Bierze się to właśnie z klasy ValidationAttribute
. Zatem klasa CheckBoxCheckedAttribute
też ma te właściwości. Więc możesz napisać:
class RegisterUserViewModel
{
[CheckBoxChecked(ErrorMessage = "Musisz zaakceptować warunki")]
public bool TermsAndConditions { get; set; }
}
lub (w aplikacji wielojęzycznej):
class RegisterUserViewModel
{
[CheckBoxChecked(ErrorMessageResourceName = nameof(LangRes.ValidationError_AcceptTerms), ErrorMessageResourceType = typeof(LangRes))]
public bool TermsAndConditions { get; set; }
}
Jeśli nie wiesz, jak zrobić lokalizację do swojego programu, sprawdź ten artykuł
wOczywiście musisz pamiętać, żeby w metodzie AddValidation
zwrócić poprawny komunikat. I teraz uwaga. Niezależnie od tego, czy posłużysz się ErrorMessage
, czy ErrorMessageResourceName
i ErrorMessageResourceType
, klasa ValidationAttribute
przechowa już dla Ciebie konkretny komunikat. Trzyma to we właściwości ErrorMessageString
, a więc zamiast komunikatu bezpośrednio w kodzie powinieneś się posłużyć tą właściwością:
public void AddValidation(ClientModelValidationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-chboxreq", ErrorMessageString);
}
Przejdźmy na kolejny poziom
Jeśli nie do końca rozumiesz ten artykuł albo chcesz czegoś więcej, przy okazji napisałem jak zrobić walidator do sprawdzania rozmiaru przesyłanego pliku. Zachęcam do zapoznania się z tym artykułem o przesyłaniu plików w Razor, Blazor i WebApi.
To tyle.
Jeśli masz jakieś wątpliwości lub znalazłeś błąd w artykule, podziel się w komentarzu.