Walidacja Danych w Minimal APIs .NET 10
Minimalne API zawsze były alternatywą dla lean w porównaniu z kontrolowanym API ASP.NET Core, ale długo miały zauważalną lukę: brak wbudowanego wsparcia dla walidacji przychodzących danych. Albo spinałeś ręczne kontrole w każdym handlerze, albo polegałeś na bibliotekach firm trzecich. .NET 10 zatyka tę lukę za pomocą walidacji adnotacji danych pierwszej klasy dla ciągów zapytań, nagłówków i ciał żądań, wszystko bez dodatkowych pakietów.
Ten głęboki przegląd .NET 10 Minimal APIs, oparty na prezentacji Tima Coreya, wyjaśnia, jak zarejestrować usługę walidacyjną, adnotacje klas modeli oraz unikać typowych pułapek modyfikatorów dostępu, które mogą zepsuć twój setup.
Konfiguracja: Prosty Minimal API
[0:44 - 1:35] Demo zaczyna się od podstawowego projektu ASP.NET Core Minimal API działającego na .NET 10. Obszar powierzchniowy jest celowo mały: dwa punkty końcowe POST, każdy akceptujący inny model.
app.MapPost("/person", (Person person) => Results.Ok(person));
app.MapPost("/login", (LoginModel login) => Results.Ok(login));
app.MapPost("/person", (Person person) => Results.Ok(person));
app.MapPost("/login", (LoginModel login) => Results.Ok(login));
Model Person zawiera podstawowe pola tożsamości. LoginModel obsługuje dane uwierzytelniające: adres e-mail, hasło i pole potwierdzenia hasła. Obydwa są wysyłane jako ciała JSON. W tym momencie nie ma żadnych kontroli wejściowych; API akceptuje cokolwiek otrzyma, w tym puste ciągi i błędnie sformatowane adresy e-mail.
Scalar (nowoczesna UI OpenAPI, która jest dostarczana z .NET 10) jest używana do uruchamiania testowych żądań bezpośrednio z przeglądarki, co ułatwia zobaczyć, co dokładnie API zwraca przed i po podłączeniu walidacji.
Rejestrowanie uslugi walidacyjnej
[2:36 - 3:06] Zanim jakiekolwiek atrybuty walidacji zadziałają, musisz je włączyć na poziomie usługi. Pojedyncze wywołanie w bloku rejestracji usługi włącza egzekwowanie na wszystkich punktach końcowych Minimal API:
builder.Services.AddValidation();
builder.Services.AddValidation();
Ta jedna linia jest całym krokiem konfiguracji. Nie ma dodatkowego oprogramowania pośredniego do dodania ani etapu potoku, do którego trzeba się podpiąć. Framework przejmuje kontrolę automatycznie, gdy usługa jest zarejestrowana. Jeśli pominiesz to wywołanie, Twoje atrybuty adnotacji danych będą obecne w modelu, ale nigdy nie będą oceniane, a żądania przechodzą niezależnie od tego, co zawierają.
Warto mieć to na uwadze jako pierwszy krok debugowania: jeśli weryfikacja cicho nic nie robi, AddValidation() jest zwykle brakującym elementem.
Dodawanie walidacji do modelu klasowego
[3:00 - 3:55] Gdy usługa jest zarejestrowana, dodanie walidacji do modelu opartego na klasie sprowadza się do dekorowania właściwości atrybutami adnotacji danych:
public class Person
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}
public class Person
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}
Po dodaniu dyrektywy System.ComponentModel.DataAnnotations u góry pliku, oznaczenie FirstName i LastName jako [Required] zapewnia, że zostaną zweryfikowane. Wysłanie żądania POST z pustym ciałem przez Scalar teraz zwraca 400 Bad Request ze złożoną odpowiedzią błędu:
{
"errors": {
"FirstName": ["The FirstName field is required."],
"LastName": ["The LastName field is required."]
}
}
Nie ma potrzeby obsługi błędów niestandardowych, brak atrybutów filtrów. Framework generuje tę odpowiedź automatycznie, a sprawdzenie odbywa się przed wykonaniem ciała procedury obsługi, więc nigdy nie musisz strzec się przed null wewnątrz logiki punktu końcowego.
Stosowanie walidacji do rekordu
[4:29 - 5:30] Te same atrybuty działają na rekordach w C#, ale składnia różni się nieco, ponieważ właściwości rekordów są zazwyczaj definiowane w głównym konstruktorze, a nie jako oddzielne deklaracje członków.
public record LoginModel(
[Required] [EmailAddress] string Email,
[Required] string Password,
[Required] string ConfirmPassword
);
public record LoginModel(
[Required] [EmailAddress] string Email,
[Required] string Password,
[Required] string ConfirmPassword
);
Atrybuty na parametrach konstruktora są stosowane do wygenerowanych właściwości, więc [Required] i [EmailAddress] zachowują się dokładnie tak samo jak w klasie. Wysłanie żądania z nieprawidłowym adresem e-mail, na przykład "notanemail", teraz zwraca 400, który identyfikuje pole Email jako nieprawidłowe.
Atrybut [Compare] może być również użyty do wymuszenia dopasowania ConfirmPassword do Password. W przypadku rekordu cele atrybutów wymagają wyraźnego potwierdzenia, ponieważ kompilator musi wiedzieć, że chodzi o wygenerowanego członka, a nie o sam parametr konstruktora:
[property: Compare(nameof(Password))]
string ConfirmPassword
[property: Compare(nameof(Password))]
string ConfirmPassword
Cel [property:] informuje kompilator, aby przypisał atrybut do wygenerowanego członka, a nie do parametru. Bez niego, [Compare] kompiluje się, ale nigdy nie działa podczas sprawdzania. To jest najtrudniejsza część pracy z rekordami vs klasami w tym kontekście: właściwości klas akceptują atrybuty naturalnie, podczas gdy parametry rekordów potrzebują wyraźnego celu dla wszystkiego, co działa na poziomie członka.
Wymóg modyfikatora dostępu publicznego
[7:51 - 9:00] Często popełniany błąd łatwo przegapić i nie generuje informacji o błędzie, gdy na niego trafisz. System weryfikacji używa refleksji do inspekcji typów modelu w czasie wykonywania; aby refleksja mogła znajdować członków typu, sam typ musi być oznaczony jako public.
// Validation will NOT run; the class is internal by default
class Person { ... }
// Validation runs correctly
public class Person { ... }
// Validation will NOT run; the class is internal by default
class Person { ... }
// Validation runs correctly
public class Person { ... }
Ta sama zasada dotyczy rekordów. Jeśli Twój model zadeklarowany jest bez modyfikatora dostępu, C# domyślnie ustawia go na internal, a serwis całkowicie go pomija. Twój punkt końcowy nadal otrzymuje żądanie, procedura obsługi nadal się wykonuje i nie zostaje zwrócony żaden błąd; sprawdzenia nic nie robią po cichu.
To jest zachowanie ASP.NET Core, które nie jest specyficzne dla Minimal APIs, ale pojawia się częściej w tym przypadku, ponieważ te projekty zwykle są zwarte, a programiści czasami definiują modele w linii lub w tym samym pliku co Program.cs bez myślenia o widoczności.
Szybki sposób audytu Twojego projektu: każdy typ modelu przekazywany do obsługi endpointa powinien być wyraźnie oznaczony jako public. Jeśli nie jest, żadna liczba atrybutów nie spowoduje, że framework zadziała.
Co możesz walidować
Wbudowane atrybuty adnotacji danych obejmują najczęściej występujące scenariusze bez dodatkowej pracy:
[Required]odrzuca wartości null lub puste[EmailAddress]weryfikuje format ciągu e-maila[Compare]sprawdza zgodność dwóch właściwości, co jest przydatne do potwierdzenia hasła[Range]wymusza granice numeryczne lub datowe[StringLength]ogranicza długość ciągu z opcjonalnym minimum[RegularExpression]weryfikuje zgodność z niestandardowym wzorcem
Wszystkie te atrybuty działają na parametrach ciągu zapytań, nagłówkach żądań i ciałach JSON. Ta sama klasa modelu lub rekord może być powiązany z różnych źródeł bez zmiany jakichkolwiek jego atrybutów.
Wnioski
[7:46 - end] Z zarejestrowanym AddValidation() i udekorowanymi modelami, Minimal APIs automatycznie egzekwują ograniczenia wejściowe zarówno w klasach, jak i zapisach. Aby to działało w .NET 10, wystarczy raz wywołać builder.Services.AddValidation(), udekorować właściwości modelu standardowymi atrybutami adnotacji danych i upewnić się, że każdy typ modelu jest oznaczony jako public.
Cel [property:] na zapisach i wymóg modyfikatora public to jedyne prawdziwe pułapki. Łatwo je przeoczyć i generują ciche błędy zamiast błędów kompilacji, więc miej je na liście kontrolnej zawsze, gdy sprawdzenia wejściowe wydają się nic nie robić.
Obejrzyj cały film na kanale YouTube Tim Coreya kanał aby poznać pełny przewodnik po kodzie źródłowym.
