Fluent Validation: Як зареєструвати всі валідатори в проекті .NET Core

pic

FluentValidation — це бібліотека валідації для .NET Core Project, яка дозволяє визначати правила для ваших моделей. Вона відокремлює логіку валідації від решти вашого коду і надає високо гнучкий і зрозумілий спосіб забезпечити правильність ваших даних перед їх обробкою.

Анотації даних є переважною і простою у використанні структурою, але клас, який функціонально спроектовано як сутність, також піддається валідації, що суперечить "Принципу єдиної відповідальності" (Single Responsibility Principle), одному з Принципів SOLID, про які я згадував у моїй попередній статті. Тому було б корисніше створити окремий клас для виконання процесу валідації.

API Документація

Для отримання додаткової інформації про FluentValidation та її можливості, перегляньте офіційну Документацію FluentValidation і посилання на GitHub.

Додавання пакетів

Спочатку створіть проект .NET Core. Потім встановіть пакет FluentValidation.AspNetCore (включає залежності FluentValidation та FluentValidation.DependencyInjectionExtensions бібліотеки) через NuGet:

pic

Якщо ви додаєте пакет через Package Manager Console, введіть наступну команду в консолі:

Install-Package FluentValidation.AspNetCore

Якщо ви додаєте пакет через .NET CLI, введіть наступну команду в консолі:

dotnet add package FluentValidation.AspNetCore

Реєстрація Сервісу

Цей ValidationMiddleware є власною реалізацією фільтра валідації для .NET Core. Він забезпечує, щоб вхідні запити з недійсними станами моделей перехоплювались і оброблялись до досягнення бізнес-логіки.

using FluentValidation;  
using Microsoft.AspNetCore.Mvc.Filters;  

namespace SM.Core.Common.Middleware  
{  
 public class ValidationMiddleware : IAsyncActionFilter  
 {  
 public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)  
 {  
 if (!context.ModelState.IsValid)  
 {  
 var errorsInModelState = context.ModelState.Where(p => p.Value.Errors.Count > 0)  
 .ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value.Errors  
 .Select(p => p.ErrorMessage)).ToArray();  
 var list = errorsInModelState.SelectMany(error => error.Value).ToList();  
 if (list.Count != 0)  
 {  
 var errorLines = list.Select(s => s).Distinct().Aggregate((current, next) => current + "\n" + next);  
 throw new ValidationException(errorLines);  
 }  
 }  
 await next();  
 }  
 }  
}

Цей клас BusinessStartup централізує конфігурацію для FluentValidation та інших налаштувань бізнес-логіки в бізнес-шарі всередині проекту .NET Core.

  • AddFluentValidationAutoValidation(): Включає автоматичну валідацію моделей за допомогою FluentValidation. Це оновлений метод, який замінює застарілий AddFluentValidation().
  • AddFluentValidationClientsideAdapters(): Включає адаптери валідації для клієнтської сторони для інтеграції з JavaScript фреймворками або бібліотеками.
  • AddValidatorsFromAssembly(typeof(BusinessStartup).Assembly): Реєструє всі класи валідаторів (які реалізують IValidator) з асемблеї, де розташований клас BusinessStartup.
    Вона динамічно виявляє та реєструє валідатори, що полегшує керування кількома валідаторами.
using FluentValidation;  
using FluentValidation.AspNetCore;  
using Microsoft.AspNetCore.Mvc;  
using Microsoft.Extensions.DependencyInjection;  
using SM.Core.Common.Middleware;  

namespace SM.AuthorizationMgmt.Business  
{  
 public static class BusinessStartup  
 {  
 public static IServiceCollection AddBusinessStartup(this IServiceCollection services)  
 {  
 //Налаштування FluentValidation  

 //// Після: Включення лише автованої валідації  
 services.AddFluentValidationAutoValidation();  
 //// Після: Включення лише клієнтської валідації:  
 services.AddFluentValidationClientsideAdapters();  
 //services.AddValidatorsFromAssemblyContaining(ServiceLifetime.Transient);  
 services.AddValidatorsFromAssembly(typeof(BusinessStartup).Assembly);  
 services.AddControllers(option => option.Filters.Add());  

 //AddFluentValidation() [застаріло]  
 //services.AddControllers(options =>  
 //{  
 // options.Filters.Add();  
 //}).AddFluentValidation(configuration => configuration.RegisterValidatorsFromAssembly(typeof(BusinessStartup).Assembly));  

 //[ApiController] тег запобігає за замовчуванням поверненню BadRequest  
 services.Configure(options => options.SuppressModelStateInvalidFilter = true);  

 return services;  
 }  
 }  
}

Опис моделі

Наступний приклад використовуватиме об'єкт RegisterUserCommand.

using MediatR;  
using SM.Core.Utilities.Results;  

namespace SM.AuthorizationMgmt.Business.Features.Authorizations.Commands  
{  
 public class RegisterUserCommand : IRequest  
 {  
 public string Username { get; set; }  
 public long CitizenId { get; set; }  
 public string Name { get; set; }  
 public string Surname { get; set; }  
 public string Email { get; set; }  
 public string MobilePhone { get; set; }  
 public string Password { get; set; }  
 public string ConfirmPassword { get; set; }  
 }  
}

Створення класу валідатора

Давайте створимо клас валідатора, який успадковується від AbstractValidator, де T — це наш клас RegisterUserCommand.
Описуйте правила валідації всередині класу валідатора.

using FluentValidation;  
using SM.AuthorizationMgmt.Business.Features.Authorizations.Commands;  
using SM.Core.Common.Constants;  

namespace SM.AuthorizationMgmt.Business.Features.Authorizations.Validations  
{  
 public class RegisterUserValidator : AbstractValidator  
 {  
 public RegisterUserValidator()  
 {  
 RuleFor(x => x.Username)  
 .NotEmpty().WithMessage("{PropertyName} " + Messages.CannotBeEmpty)  
 .Length(2, 100).WithMessage("{PropertyName} " + string.Format(Messages.MustBeBetweenCharacter, "{MinLength}", "{MaxLength}"));  

 RuleFor(x => x.CitizenId)  
 .NotEmpty().WithMessage("{PropertyName} " + Messages.CannotBeEmpty)  
 .Must(Be11Digits).WithMessage("{PropertyName} " + string.Format(Messages.MustBeCharacter, "11"));  
 RuleFor(x => x.CitizenId)  
 .Must(ValidateCitizenId).WithMessage("{PropertyName} " + Messages.IsNotValid)  
 .When(x => Be11Digits(x.CitizenId));  

 RuleFor(x => x.Name)  
 .NotEmpty().WithMessage("{PropertyName} " + Messages.CannotBeEmpty)  
 .Length(2, 100).WithMessage("{PropertyName} " + string.Format(Messages.MustBeBetweenCharacter, "{MinLength}", "{MaxLength}"));  
 RuleFor(x => x.Surname)  
 .NotEmpty().WithMessage("{PropertyName} " + Messages.CannotBeEmpty)  
 .Length(2, 100).WithMessage("{PropertyName} " + string.Format(Messages.MustBeBetweenCharacter, "{MinLength}", "{MaxLength}"));  
 RuleFor(x => x.Email)  
 .NotEmpty().WithMessage("{PropertyName} " + Messages.CannotBeEmpty)  
 .EmailAddress().WithMessage("{PropertyName} " + Messages.IsNotValid);  
 RuleFor(x => x.MobilePhone)  
 .NotEmpty().WithMessage("{PropertyName} " + Messages.CannotBeEmpty)  
 .Matches(@"^\d{10}$").WithMessage("{PropertyName} " + Messages.IsNotValid);  
 RuleFor(x => x.Password)  
 .NotEmpty().WithMessage("{PropertyName} " + Messages.CannotBeEmpty)  
 .Equal(x => x.ConfirmPassword).WithMessage(Messages.PasswordsDoNotMatch);  
 RuleFor(x => x.ConfirmPassword).NotEmpty().WithMessage("{PropertyName} " + Messages.CannotBeEmpty);  
 }  

 // Перевірка, чи має число рівно 11 цифр  
 private bool Be11Digits(long citizenId)  
 {  
 return citizenId.ToString().Length == 11;  
 }  

 // Користувацька логіка валідації  
 private bool ValidateCitizenId(long citizenId)  
 {  
 string citizenIdStr = citizenId.ToString();  

 // Перетворення в масив цифр  
 int[] digits = citizenIdStr.Select(digit => int.Parse(digit.ToString())).ToArray();  

 // Обчислення суми перших 10 цифр  
 int sum = digits.Take(10).Sum();  

 // Перевірка, чи сума за модулем 11 дорівнює останній цифрі  
 return sum % 10 == digits[10];  
 }  
 }  
}

У цьому проєкті використовуються приклади основних правил (NotEmpty, Length, Must, When, EmailAdress, Matches, Equal) та користувацьких методів (Be11Digits, ValidateCitizenId). WithMessages: Користувацькі повідомлення використовують {PropertyName} для динамічного іменування властивостей і Messages для узгодженого локалізованого повідомлення про помилки, яке показується клієнту.

Реєстрація валідатора

Якщо ви використовуєте Web API, вам потрібно зареєструвати ваш валідатор в Service Provider у класі Web API Program.cs (.Net Core 8 Project). Валідація автоматично активується у ваших діях контролера без необхідності вручну викликати валідатори.

var builder = WebApplication.CreateBuilder(args);  

// Додайте сервіси в контейнер.  
builder.Services.AddControllers();  
builder.Services.AddBusinessStartup();

Приклад використання

pic

Коли ви робите POST-запит з неправильними введеними даними, ви отримаєте результат ValidationException.

pic

Висновок

FluentValidation — це чудовий інструмент для спрощення валідації в додатках .NET Core.
Окремо виділяючи логіку валідації від решти вашого бізнес-коду, ви створюєте більш чисті та зручні для підтримки додатки.

Сподіваюся, що ця стаття допомогла вам зрозуміти, як побудувати додаток з Fluent Validation у .NET Core! Повний вихідний код ви можете знайти на моєму репозиторії GitHub. Не соромтеся додавати зірочки, форкати або вносити свій внесок у проєкт.

https://github.com/58mustafasahin/SahinStockManager

Щасливого кодування!

Перекладено з: Fluent Validation: How To Register All Validators in .NET Core Project

Leave a Reply

Your email address will not be published. Required fields are marked *