Walidacja po stronie backendu

0

Cześć,

Mam problem z walidacją po stronie backendu. Poniżej mój kontroler:

public class AddDietModel : PageModel
    {
        private List<string> errorMsg = new List<string>();
        private readonly IDietRepository dietRepository;
        private readonly IMapper mapper;

        [BindProperty]
        public Models.Models.DietDTO Diet { get; set; }
        public AddDietModel(
            IDietRepository dietRepository,
            IMapper mapper)
        {
            this.dietRepository = dietRepository;
            this.mapper = mapper;
        }

        public IActionResult OnGet()
        {
            return Page();
        }

        public IActionResult OnPost()
        {
            List<string> _errorMessages = isModelIsValid();
            if (_errorMessages.Count > 0)
            {
                return RedirectToPage();
            }
            var diet = mapper.Map<Models.Diet>(Diet);
            dietRepository.AddDiet(diet);
            return RedirectToPage("Index");
        }

        private List<string> isModelIsValid()
        {
            if (Diet.DietName.Length <= 5)
            {
                errorMsg.Add("Nazwa diety musi mieć minimum 5 znaków");
                ModelState.AddModelError("DietNameMin5", "Nazwa diety musi mieć minimum 5 znaków");
            }

            if (errorMsg.Count == 0)
            {
                return null;
            }
            else
            {
                return errorMsg;
            }
        }
    }

Na froncie z kolei:

@page
@model MealApp.Pages.Diet.AddDietModel
@{
    string validationClass = "invalid-feedback";
}
<h1>Dodawanie diety</h1>

<form method="post" class="mt-3">
    <div asp-validation-summary="ModelOnly"></div>
    <div class="form-group row">
        <label asp-for="@Model.Diet.DietName" class="col-sm-2 col-form-label">
            Nazwa diety
        </label>
        <div class="col-sm-8">
            <input asp-for="@Model.Diet.DietName" class="form-control val" />
            <span asp-validation-for="@Model.Diet.DietName" class="text-danger"></span>
            @Html.ValidationMessage("DietNameMax30", "", new { @class = validationClass })
            @Html.ValidationMessage("DietNameMin5", "", new { @class = validationClass })
        </div>
        
    </div>
    <div class="form-group row">
        <label asp-for="@Model.Diet.Description" class="col-sm-2 col-form-label">
            Ogólny opis diety
        </label>
        <div class="col-sm-8">
            <textarea rows="5" asp-for="@Model.Diet.Description" class="form-control"></textarea>
        </div>
    </div>
    <div class="form-group row">
        <div class="col-sm-10">
            <button class="btn btn-primary" type="submit">Utwórz dietę</button>
            <a asp-page="/Diets/Index" class="btn btn-danger">Anuluj</a>
        </div>
    </div>
</form>
@section Scripts {
    <partial name="~/Pages/Shared/_ValidationScriptsPartial.cshtml" />
}

Helper Html.ValidationMessage po wyrenderowaniu występuje w kodzie zarówno przy pierwszym uruchomieniu strony jak i po wygenerowaniu błędu, ale nie wyświetla się żadna informacja pod inputem.

<span class="field-validation-valid invalid-feedback" data-valmsg-for="DietNameMin5" data-valmsg-replace="true"></span>

Ktoś jest w stanie pomóc?

0

A to nie jest przypadkiem tak, że ty ustawiasz jaki ma być message error

ModelState.AddModelError("DietNameMin5", "Nazwa diety musi mieć minimum 5 znaków");

A w HTML go "zerujesz"

@Html.ValidationMessage("DietNameMax30","", new { @class = validationClass })

Nie piszę w razor. Ciężko mi cokolwiek przetestować. Możesz przypisać mu klasę stylu nie podając message error, np tak:

@Html.ValidationMessage("DietNameMax30", new { @class = validationClass })
2

Walidacja po stronie backendu generalnie jest prosta. Ale powinieneś też dodać walidację na froncie. No bo po co wysyłać do serwera niepoprawne dane? Przekleję Ci tu artykuł, który napisałem dosłownie kilka dni temu:

Walidacja danych w asp mvc .netcore 3

Czym jest walidacja
Walidacja to po prostu sprawdzenie poprawności danych podanych przez użytkownika. Walidacja jest ściśle powiązana z jakimś formularzem, który użytkownik wypełnia.
Np. może być to po prostu rejestracja nowego konta. Wtedy taka walidacja polega na sprawdzeniu, czy użytkownik wypełnił wszystkie wymagane pola, czy jego hasło i nazwa użytkownika
spełniają nasze założenia (np. musi być wielka litera albo nazwa użytkownika musi być adresem e-mail) no i oczywiście, czy zaakceptował naszą politykę prywatności i regulamin :)

Walidacja jest do tego, żeby nie dopuścić do sytuacji, w której w bazie danych znajdują się głupoty. Chroni też przed jakąś spójnością danych, a także przed pewnymi atakami.
Także każdy formularz wypełniany przez użytkownika powinien być zwalidowany. Pamiętaj, że użytkownik może wpisać wszystko, co mu się podoba. To na Tobie w pewnym sensie leży odpowiedzialność
sprawdzenia, czy to, co wpisał ma sens.

Są dwa "tryby" walidacji. Po stronie serwera i po stronie klienta.

Walidacja po stronie klienta
Walidacja po stronie klienta następuje przed wysłaniem danych do serwera. Czyli np. w przeglądarce internetowej lub aplikacji. Mamy formularz rejestracji, użytkownik wciska guzik "Rejestruj" i w tym momencie musimy sprawdzić poprawność danych. Jeśli dane nie są poprawne, wtedy wywalamy stosowny komunikat. Jeśli uznamy, że dane są ok - wysyłamy je do serwera. Jeśli chodzi o aplikacje internetowe, to
takim sprawdzeniem zajmuje się np. JavaScript. Czyli musimy napisać odpowiedni kod w tym... bleee... języku, który sprawdzi nam poprawność wpisanych danych.

Walidacja po stronie serwera.
No i tutaj mamy to, co backendowcy lubią najbardziej. Czyli dostajemy dane od klienta i za chwilę wrzucimy je do bazy danych. Ale, ale... Jeden z moich profesorów na studiach mawiał
"Kto szybko daje, ten dwa razy daje". Zatem nie możemy do końca ufać danym, które otrzymaliśmy. Musimy sprawdzić je drugi raz. I tu wchodzi walidacja po stronie serwera - dopiero jeśli tutaj upewnimy się, że wszystko jest ok, możemy wbić dane do bazy lub zrobić z nimi coś innego.

Czyli krótko podsumowując, proces powinien wyglądać tak:

  • Użytkownik wklepuje dane i wciska guzik "OK"
  • Następuje walidacja po stronie klienta (JavaScript)
  • Jeśli walidacja jest ok, to wysyłamy dane do serwera, jeśli nie, to mówimy użytkownikowi, że coś zje... źle wprowadził
  • Po stronie serwera odbieramy dane i SPRAWDZAMY JE JESZCZE RAZ
  • Jeśli dane są ok, to wbijamy je do bazy danych i odpowiadamy klientowi: "Ok, wszystko się udało". Jeśli dane są złe, odpowiadamy: "Hola hola, coś tu źle wprowadziłeś".

Taka podwójna walidacja nie jest w prawdzie konieczna. Możesz tworzyć systemy jak Ci się podoba. Ale jeśli chcesz ograniczyć dziury, błędy i podatności na ataki w swoim systemie, podwójna walidacja jest obowiązkiem. Pamiętaj, że nie zawsze dostaniesz dane z własnej aplikacji. Czasem ktoś po prostu wyśle "na pałę". Dlatego walidacja po stronie serwera jest konieczna. Nie opłaca się też nikomu wysyłać na serwer danych, które wiadomo, że są niepoprawne. Bardziej opłaca się sprawdzić je po stronie klienta przed wysłaniem.

Na szczęście .NetCore ma pewne mechanizmy do walidacji danych, które teraz pokrótce Ci pokażę. Walidacja w .NetCore składa się z trzech etapów:

  • odpowiednie przygotowanie modelu
  • wywołanie walidacji po stronie klienta
  • wywołanie walidacji po stronie serwera

Przygotowanie modelu.
Załóżmy, że mamy klasę, która przechowuje dane rejestracyjne użytkownika (model), możemy jej poszczególne właściwości ubrać w konkretne atrybuty. To jest tzw. "annotation validation", czyli
walidacja obsługiwana za pomocą pewnych "adnotacji".

Spójrzmy na tę "gołą" klasę:

class RegisterUserViewModel
{
    public string UserName { get; set; }
	public string Password { get; set; }
	public string RepeatedPassword { get; set; }
}

Musimy się upewnić, że:

  • wypełniona jest nazwa użytkownika
  • wypełnione jest hasło
  • podane hasła są identyczne

Za pierwsze 2 założenia odpowiada atrybut Required:

//using System.ComponentModel.DataAnnotations;

class RegisterUserViewModel
{
    [Required]
	public string UserName { get; set; }
	
	[Required]
	public string Password { get; set; }
	
	[Required]
	public string RepeatedPassword { get; set; }
}

Teraz, gdy uruchomimy proces walidacji, sprawdzi on te pola. To wygląda mniej więcej tak:

Walidator: Cześć obiekcie, jakie masz pola?
Obiekt: No hej, mam UserName, Password i PasswordRepeated
Walidator: Ok, jakie masz atrybuty walidacyjne na polu UserName?
Obiekt: Required
Walidator: Hej wielki walidatorze Required! Czy pole UserName w danym obiekcie spełnia Twoje założenia?

Wtedy walidator required sprawdza. Taki walidator mógłby wyglądać w taki sposób (zakładając, że byłby tylko dla stringa, ale jest dla innych typów też):

	return !string.IsNullOrWhitespace(value);

Walidator zrobi analogiczne sprawdzenie przy pozostałych polach.

Ok, teraz w drugim kroku chcemy, żeby pole UserName było poprawnym adresem e-mail. Można się do tego posłużyć atrybutem.... EmailAddress:

class RegisterUserViewModel
{
    [Required]
	[EmailAddress]
	public string UserName { get; set; }
	
	[Required]
	public string Password { get; set; }
	
	[Required]
	public string RepeatedPassword { get; set; }
}

Walidacja adresu e-mail
Poświęciłem na to osobny akapit (chociaż zastanawiam się nad artykułem).
Zasadniczo wiemy, jakie są poprawne adresy e-mail:
[email protected]
[email protected]
[email protected]

Natomiast niepoprawnymi mogą być:
@@@@
jkowalski(at)gmail.com
pitu-pitu@pl

Do wersji .NetFramework 4.7.2 atrybut EmailAddress działał tak, że sprawdzał adres e-mail za pomocą wyrażeń regularnych. Wyrażenia regularne mają jedną mała wadę - są stosunkowo drogie, jeśli chodzi o zasoby - wolne. To jest pewna furtka dla ataku DoS (denial of service). Atak ten polega na tym, że działamy na serwerze w taki sposób, że całkowicie go blokujemy. Serwer nie może służyć już innym użytkownikom. Okazało się, że duża ilość stringów a co gorsza dużych stringów przepychana przez ten walidator, może właśnie mieć takie działanie. Wyrażenia regularne żrą sporo, więc duża ilość dużych stringów może zablokować serwer. Dlatego Microsoft zmienił trochę sposób działania tego walidatora. Teraz uznaje, że każdy string, który ma tylko jedną małpę i nie jest ona ani na końcu, ani na początku jest poprawnym adresem e-mail, czyli:

a@b - poprawny
[email protected] - poprawny
@blbla - niepoprawny
abc@ - niepoprawny

No i tutaj właśnie zapala się lampka: "a@b" ma być poprawnym e-mailem? No właśnie nie do końca. Ale to sprawdzenie miało być szybkie. Jest szybkie. Ale skoro jest szybkie, to jest też proste.
Zasadniczo sprawdza czy podany string MOŻE być poprawnym adresem e-mail.

Można to jednak zmienić. W pliku appsettings trzeba dodać taką linijkę:
<add key="dataAnnotations:dataTypeAttribute:disableRegEx" value="false" />

Wtedy sprawdzenie adresu e-mail będzie po staremu za pomocą wyrażeń regularnych. Stosuj tylko wtedy, kiedy wiesz co robisz. W gruncie rzeczy to użytkownik powinien podać Ci swój właściwy adres e-mail.

Ok, skoro załatwiliśmy już e-mail, sprawdźmy teraz czy użytkownik podał dwa razy to samo hasło, czy może coś znowu schrzanił:

class RegisterUserViewModel
{
    [Required]
	[EmailAddress]
	public string UserName { get; set; }
	
	[Required]
	public string Password { get; set; }
	
	[Required]
	[Compare("Password")]
	public string RepeatedPassword { get; set; }
}

Jak widzisz odpowiada za to atrybut Compare. W parametrze (otherProperty) podajemy nazwę pola z tej klasy, z którą ma zostać to pole porównane. Tutaj "Password". Czyli porównamy pole "RepeatedPassword" z polem "Password". Tylko tutaj też uwaga. Jeśli chodzi o porównanie dwóch stringów, to wykorzystywana jest tu metoda Equals z klasy string. Ta metoda nie jest wrażliwa na ustawienia językowe. Tzn. że w
pewnych językach i w pewnych warunkach może stwierdzić, że dwa różne stringi są takie same lub dwa takie same stringi są różne.

Teraz dodatkowo możemy upewnić się, czy użytkownik jest pełnoletni. Do tego może posłużyć atrybut Range, który oznacza, że wartość powinna być z konkretnego zakresu. A więc użytkownik może na przykład podać swój wiek:

class RegisterUserViewModel
{
    [Required]
	[EmailAddress]
	public string UserName { get; set; }
	
	[Required]
	public string Password { get; set; }
	
	[Required]
	[Compare("Password")]
	public string RepeatedPassword { get; set; }
	
	[Required]
	[Range(18, 50)]
	public int Age { get; set; }
}

W powyższym przykładzie sprawdzamy, czy wiek użytkownika jest między 18 a 50 lat. Atrybut Range musi być zastosowany z typem int lub datą. Przy czym stosowanie go z datą nie ma raczej uzasadnienia,
ponieważ musielibyśmy wklepać ją na sztywno, co może przysporzyć sporych problemów w przyszłości, np:

[Range(typeof(DateTime), "08-06-1984")]
public object BirthDate { get; set; }

Jak widzisz, mamy tu przynajmniej 2 problemy. Pierwszy to oczywiście format daty (chociaż z tym można sobie poradzić), a drugi to data wpisana na sztywno. Zakładasz tutaj, że użytkownik musi być urodzony po 8 czerwca 1984 roku.
Ale za rok to ograniczenie będzie już bez sensu. Dlatego stosuj Range raczej do zmiennych int. Możesz podać też samo minimum lub maksimum:

class RegisterUserViewModel
{
    [Required]
	[EmailAddress]
	public string UserName { get; set; }
	
	[Required]
	public string Password { get; set; }
	
	[Required]
	[Compare("Password")]
	public string RepeatedPassword { get; set; }
	
	[Required]
	[Range(Minimum = 18)]
	public int Age { get; set; }
}

(analogicznie istnieje właściwość Maximum).

Pewnie chciało by się zapytać - jak atrybut range traktuje swoje minimum i maksimum. Czy od minimum, czy minimum jest wykluczone? Możesz sam to sprawdzić, pisząc odpowiedni kod, do czego Cię zachęcam :) Jeśli jednak trafiłeś tutaj tylko po to, żeby dowiedzieć się tej konkretnie rzeczy, to już mówię - minimum i maksimum są dopuszczone. Czyli minimum jest pierwszą liczbą, która przechodzi sprawdzenie, a maksimum - ostatnią.

Jest jeszcze kilka atrybutów, które mogą Ci się przydać. Każdy z nich jest opisany na stronach Microsoftu:

https://docs.microsoft.com/pl-pl/dotnet/api/system.componentmodel.dataannotations.validationattribute?view=net-5.0

Walidacja po stronie serwera - jak?
Generalnie w .NetCore nie ma chyba nic prostszego. Robi się to w kontrolerze (to jedna z możliwości) - niezależnie od tego, czy pracujesz nad WebApi, czy nad stroną (Razor Views). Działa to tak:

(przykład dla RazorView, dla WebAPI jest to analogicznie)

public class MyController: Controller
{
	[HttpPost]
	public async Task<IActionResult> RegisterUser(RegisterUserViewModel model)
	{
	    if(!ModelState.IsValid)
			return BadRequest();
	}
}

I w zasadzie to wszystko. Kontroler ma taki obiekt ModelState, który przechowuje informacje na temat poprawności przekazanego modelu. Właściwość IsValid określa, czy model jest poprawnie wypełniony (true), czy nie (false). Możesz też poznać wszystkie błędy obecne w modelu, ale uwaga. Na tym etapie (tuż przed dodaniem na serwer) raczej nie informowałbym użytkownika o szczegółowych błędach (chociaż to zależy od Ciebie - Ty wiesz co klient usiłuje zrobić i jak istotne i tajne powinny być dane w każdym przypadku). Jesteśmy w końcu na serwerze, a ktoś te dane na serwer musiał wysłać. Więc albo zrobił to źle klient - wtedy musimy poprawić klienta (bo np. zabrakło walidacji po stronie klienta), albo ktoś próbuje nam w jakiś sposób zaszkodzić. Przy WebAPI jest jeszcze inna opcja - ktoś po prostu tworzy aplikację i nie poradził sobie z poprawnym wysłaniem danych... No cóż... Musi doczytać w dokumentacji.

Walidacja w RazorPages wygląda też analogicznie. Tutaj obiekt ModelState też istnieje z tym, że w klasie PageModel, np:

public class MyPage: PageModel
{
	public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
			return BadRequest();
			
        return Page();
    }
}

Walidacja po stronie klienta
Tutaj kwestia jest też zasadniczo prosta, jeśli chodzi przynajmniej o walidację typową - dostępną w .NetCore.
Gdzieś tam na początku mówiłem, że walidacja po stronie klienta wymaga JavaScriptu. I to niestety prawda. Na szczęście Microsoft stworzył taką bibliotekę jQuery unobtrusive validation.
Ona jest stworzona w taki sposób, żeby współdziałać z widokami dzięki TagHelperom.

Jeśli nie wiesz, czym są TagHelpers, to dosłownie "Tagi pomocnicze" - tagi w sensie tagi html.
Jeśli nie masz pojęcia, o tworzeniu widoków Razor, przeczytaj ten artykuł.

UWAGA! Żeby to zadziałało, musisz dodać do strony 3 skrypty:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.1/jquery.validate.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/jquery.validate.unobtrusive.min.js"></script>

Pierwszy z nich masz raczej na "dzień dobry" w widoku _Layout.cshtm, pozostałe w _ValidationScriptsPartial.cshtml
Więc domyślnie wystarczyłoby, żebyś dodał na początku strony:

<partial name="_ValidationScriptsPartial" />

Na początek musimy podać w widoku konkretny model:

@model RegisterUserViewModel

Następnie już musimy zwalidować konkretne pola w formularzu, np:

<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>

Powyżej masz fragment formularza rejestracyjnego. Jeśli nie znasz bootstrap, to tłumaczę pokrótce:
pierwszy div - tworzy "grupę" kontrolek - w tym przypadku label, input i jakiś span (Etykietę, pole do wprowadzenia danych i jakiś span).
Etykieta to po prostu tekst zachęty: "Podaj swój email:".
Teraz pole do wprowadzenia danych - input. Tutaj pojawiła się nowa rzecz - tag pomocniczy "asp-for". Jeśli wpiszesz sobie asp-for, to Intellisense pokaże Ci wszystkie pola w Twoim modelu. Skąd wie, co jest Twoim modelem? No przecież mu pokazałeś na początku widoku:

@model RegisterUserViewModel

asp-for tworzy pewnego rodzaju powiązanie między kontrolką HTML, a polem w Twoim modelu. Czyli to, co użytkownik wpisze do tej kontrolki, AUTOMAGICZNIE trafi do pola UserName w Twoim modelu. Niczego nie musisz przepisywać. No złoto...

Ale to nie wszytko. Zapewnia to też walidację. Czyli automagicznie zostanie sprawdzone Twoje pole pod kątem poprawności (w tym wypadku Required i EmailAddress).
Jeśli walidacja przejdzie, to formularz zostanie wysłanie, jeśli nie no to jakoś użytkownikowi wypadałoby powiedzieć, że znowu coś schrzanił. I od tego mamy ten tajemniczy SPAN.
Zauważ na początek jedną rzecz - span ma tag otwarcia i zamknięcia. Nie możesz napisać tak: - bez tagu zamknięcia, bo coś może nie zadziałać (może być różnie na różnych wersjach). Więc musi być tag zamknięcia.

Ten SPAN wyświetli informacje, jeśli pole zostanie błędnie wypełnione (nie przejdzie walidacji). Tag "asp-validation-for" mówi po prostu dla jakiego pola ma pokazać się informacja o błędzie. Żeby nie zrobić użytkownikowi mindfucka, podaliśmy tutaj pole UserName. Klasa "text-danger" to jest po prostu bootstrapowa klasa, która powoduje, że wyświetlony tekst będzie w kolorze czerwonym.

Czyli podsumowując:
label - etykieta dla pola, mówiąca użytkownikowi co ma wpisać
input z tagiem asp-for - pole do wpisania
span z tagiem asp-validation-for - informacja w przypadku błędu.

No właśnie, ale jaka informacja? .NetCore pokaże po prostu domyslne info takie, jakie zaprogramowali w Microsofcie. Ale MOŻESZ ustawić własne powiadomienia. Wróćmy do modelu:

class RegisterUserViewModel
{
    [Required(ErrorMessage="Nie, nie, nie. To pole MUSISZ wypełnić")]
	[EmailAddress(ErrorMessage="A takiego! Nie podałeś prawidłowego e-maila")]
	public string UserName { get; set; }
	
	[Required]
	public string Password { get; set; }
	
	[Required]
	[Compare("Password")]
	public string RepeatedPassword { get; set; }
	
	[Required]
	[Range(Minimum = 18)]
	public int Age { get; set; }
}

WSZYSTKIE atrybuty walidacyjne mają właściwość ErrorMessage, do której możesz wpisać komunikat błędu. Komunikaty mogą być też lokalizowane, np:

[Required(ErrorMessageResourceName = nameof(LangRes.Validation_RequiredField), ErrorMessageResourceType = typeof(LangRes))]

I teraz tak. ErrorMessageResourceType to jest Twój typ z zasobami językowymi, który posiada klucz Validation_RequiredField.
Jeśli nie wiesz, jak tworzyć wersje językowe, przeczytaj ten artykuł.

Jest jeszcze jeden sposób na pokazanie błędów w widoku. Możesz pokazać wszystkie błędy jeden po drugim, zamiast konkretnych błędów pod konkretnymi kontrolkami. Wystarczy zrobić to:

<form asp-action="Register" method="Post">
	<div asp-validation-summary="All"></div>
	<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>

Wtedy komunikaty o błędach pojawią się na tym dodatkowym divie w postaci listy.

To właściwie już tyle jeśli chodzi o podstawy walidacji w .NetCore.
Gratulacje, dotarłeś do końca :)

Czyli podsumowując - atrybuty w modelu + sprawdzenie ModelState.IsValid. I to wszystko.

0

Dzięki za treść. Na pewno część warta uwagi. Natomiast jeśli chodzi o dość niestandardowe walidacje to chyba trzeba klepać JS'a i nie da się wszystkiego załatwić adnotacjami.

Pytanie do innych użytkowników - czy walidacja poprzez dodanie adnotacji do modelu jest bardziej wydajna, efektywniejsza niż klepanie ifów w kodzie? Wydaje mi się na pierwszy rzut oka, że tak powinno być, ale może ktoś potwierdzi? Ostatnio pracowałem przy projekcie w firmie, w której właśnie rozwiązaniem było dodawanie ModelState.AddModelError.

1
Krispekowy napisał(a):

Pytanie do innych użytkowników - czy walidacja poprzez dodanie adnotacji do modelu jest bardziej wydajna, efektywniejsza niż klepanie ifów w kodzie?

Jest efektywniejsza podczas pisania, bo zamiast po swojemu implementować ifologię korzystasz z czegoś, co już zostało zrobione i sprawdzone.Do tego masz mniej kodu. Jest też w pewnym sensie efektywniejsza w czytaniu, bo od razu widać, co i jak jest wymagane od danej klasy.

Za to ma swoje wady, bo:

  • Kod klasy jest upstrzony nieraz wieloma takimi smutnymi atrybutami, co w pewnym momencie sprawia, że staje się mniej czytelny.
  • Wymaga stosowania magicznych stringów, np. tutaj: [Compare("Password")]. Zmienisz nazwę właściwości, walidacja przestaje działać.
  • Jest mało elastyczny, robienie czegokolwiek bardziej skomplikowanego niż najprostsze przypadki, podejmowanie jakichkolwiek dynamicznych decyzji, korzystanie z jakichś zewnętrznych zależności jest utrudnione.
  • Ciężko się testuje.

Dlatego też ja preferuję oddzielenie DTO od kodu je walidującego, i wybieram rozwiązanie pozbawione powyższych problemów: https://fluentvalidation.net/

Ostatnio pracowałem przy projekcie w firmie, w której właśnie rozwiązaniem było dodawanie ModelState.AddModelError.

Wygląda na ogromną stratę czasu.

0

@somekind: https://fluentvalidation.net/ wygląda bardzo ciekawie! Na pewno zgłębię temat. Dzięki

Co do tej straty czasu to faktycznie - fluentvalidation mogłoby to rozwiązać. Z tego co zauważyłem to właśnie ModelState.AddModelError stosowane było w celu dynamicznej walidacji.

1 użytkowników online, w tym zalogowanych: 0, gości: 1