FluentValidation — AbstractValidator, kuchli validatsiya qoidalari
Muammodan boshlaylik
Buyurtma DTO si:
public record BuyurtmaDto(
string Telefon,
string YetkazishTuri, // "kuryer" yoki "olib ketish"
string? Manzil, // kuryer bo'lsa — majburiy
DateTime? YetkazishVaqti, // kelajakda, ish vaqtida
List<BuyurtmaQatoriDto> Qatorlar); // kamida bitta, har biri to'g'ri
Qoidalar: "kuryer bo'lsa manzil majburiy", "yetkazish vaqti 9:00–21:00 oralig'ida va kelajakda", "har bir qatorda soni 1–99", "promokod bazada mavjud bo'lsin". Atributlar (46-dars) bilan — shartli qoidalar, ichki ro'yxatlar va bazaga murojaat — noqulay yoki imkonsiz. DTO atributlar bilan to'lib, o'qib bo'lmaydigan holga keladi.
FluentValidation nima?
FluentValidation — validatsiya qoidalarini DTO dan alohida klassda, zanjirli (fluent) sintaksis bilan yozish kutubxonasi:
dotnet add package FluentValidation.DependencyInjectionExtensions
using FluentValidation;
public class BuyurtmaDtoValidator : AbstractValidator<BuyurtmaDto>
{
public BuyurtmaDtoValidator()
{
RuleFor(x => x.Telefon)
.NotEmpty().WithMessage("Telefon majburiy")
.Matches(@"^\+998\d{9}quot;).WithMessage("Telefon +998XXXXXXXXX formatida");
RuleFor(x => x.YetkazishTuri)
.Must(t => t is "kuryer" or "olib ketish")
.WithMessage("Yetkazish turi: 'kuryer' yoki 'olib ketish'");
RuleFor(x => x.Manzil)
.NotEmpty().WithMessage("Kuryer uchun manzil majburiy")
.When(x => x.YetkazishTuri == "kuryer");
RuleFor(x => x.YetkazishVaqti)
.Must(v => v > DateTime.Now).WithMessage("Vaqt kelajakda bo'lsin")
.Must(v => v!.Value.Hour is >= 9 and < 21).WithMessage("Yetkazish 9:00–21:00 oralig'ida")
.When(x => x.YetkazishVaqti.HasValue);
RuleFor(x => x.Qatorlar)
.NotEmpty().WithMessage("Savat bo'sh");
RuleForEach(x => x.Qatorlar).SetValidator(new BuyurtmaQatoriValidator());
}
}
public class BuyurtmaQatoriValidator : AbstractValidator<BuyurtmaQatoriDto>
{
public BuyurtmaQatoriValidator()
{
RuleFor(x => x.MahsulotId).GreaterThan(0);
RuleFor(x => x.Soni).InclusiveBetween(1, 99);
}
}
DTO — toza, faqat ma'lumot; qoidalar — o'qishli, alohida klassda.
Hayotiy o'xshatma
Bino qurilishidagi inspektor va uning tekshiruv varaqasi. Qoidalar binoning devorlariga yopishtirilmaydi (atributlar) — inspektorning alohida, tartibli ro'yxatida turadi: "agar bino 5 qavatdan baland bo'lsa — lift majburiy", "har bir xonada — deraza". Ro'yxatni o'zgartirish — binoni qayta qurishni talab qilmaydi.
Asosiy qoidalar
| Qoida | Ma'nosi |
|---|---|
NotEmpty() / NotNull() |
Bo'sh emas / null emas |
Length(min, max), MaximumLength(n) |
Uzunlik |
GreaterThan(x), InclusiveBetween(a, b) |
Son oralig'i |
EmailAddress() |
|
Matches(regex) |
Regex |
Must(predicate) |
O'z sharti |
Equal(x => x.Parol) |
Boshqa maydonga teng |
IsInEnum() |
Enum ning to'g'ri qiymati |
.When(...) / .Unless(...) |
Shartli qoida |
.WithMessage("...") |
Xabar |
.WithName("Telefon raqam") |
Xabardagi maydon nomi |
RuleForEach(...) |
Kolleksiyaning har bir elementi |
SetValidator(...) |
Ichki ob'ekt uchun validator |
Xato bo'lganda to'xtash
RuleFor(x => x.Email)
.Cascade(CascadeMode.Stop) // birinchi xatoda keyingi qoidalarni tekshirmaslik
.NotEmpty()
.EmailAddress();
Asinxron qoidalar — bazaga murojaat
public class RoyxatDtoValidator : AbstractValidator<RoyxatDto>
{
public RoyxatDtoValidator(DokonDbContext db) // DI orqali
{
RuleFor(x => x.Email)
.NotEmpty()
.EmailAddress()
.MustAsync(async (email, ct) =>
!await db.Foydalanuvchilar.AnyAsync(f => f.Email == email.ToLower(), ct))
.WithMessage("Bu email allaqachon ro'yxatdan o'tgan");
}
}
[!WARNING] Asinxron "noyoblik" tekshiruvi — poyga holatidan (race condition) himoya qilmaydi: ikki bir vaqtdagi so'rov ikkalasi ham "bo'sh" javob olib, ikkalasi ham yozilishi mumkin. Haqiqiy kafolat — bazadagi unique index (51-dars). Validator — faqat foydalanuvchiga tushunarli xabar uchun.
Ro'yxatdan o'tkazish va ishlatish
// Program.cs — barcha validator larni avtomatik topish
builder.Services.AddValidatorsFromAssemblyContaining<Program>();
Controller da
[HttpPost]
public async Task<IActionResult> Yarat(
BuyurtmaDto dto,
[FromServices] IValidator<BuyurtmaDto> validator,
CancellationToken ct)
{
var natija = await validator.ValidateAsync(dto, ct);
if (!natija.IsValid)
{
return ValidationProblem(new ValidationProblemDetails(natija.ToDictionary()));
}
var b = await service.YaratAsync(dto, ct);
return CreatedAtAction(nameof(Ol), new { id = b.Id }, b);
}
Javob formati — 46-darsdagi bilan bir xil ProblemDetails.
Avtomatik — filtr bilan
Har bir action da qo'lda chaqirmaslik uchun — umumiy action filter yoki Minimal API endpoint filter (75-dars) yozish mumkin:
public class ValidatsiyaFiltri<T>(IValidator<T> validator) : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext ctx, ActionExecutionDelegate next)
{
var arg = ctx.ActionArguments.Values.OfType<T>().FirstOrDefault();
if (arg is not null)
{
var r = await validator.ValidateAsync(arg);
if (!r.IsValid)
{
ctx.Result = new BadRequestObjectResult(new ValidationProblemDetails(r.ToDictionary()));
return;
}
}
await next();
}
}
[!INFO] Eski
FluentValidation.AspNetCorepaketidagi "avtomatik MVC validatsiyasi" (AddFluentValidationAutoValidation) endi tavsiya etilmaydi — kutubxona mualliflari aniq, qo'lda chaqirishni (yoki o'z filtringizni) maslahat beradi, chunki avtomatik rejim asinxron qoidalarni to'g'ri qo'llab-quvvatlamaydi.
Test qilish
var validator = new BuyurtmaQatoriValidator();
var natija = validator.TestValidate(new BuyurtmaQatoriDto(MahsulotId: 1, Soni: 0));
natija.ShouldHaveValidationErrorFor(x => x.Soni);
Qoidalar — oddiy klass, server ishga tushirmasdan test qilinadi (76-dars).
Data Annotations yoki FluentValidation?
| Data Annotations | FluentValidation | |
|---|---|---|
| Qayerda | DTO property larida | Alohida klass |
| Oddiy qoidalar | ✅ juda qisqa | ✅ |
| Shartli, ichki ro'yxat, bir nechta maydon | Noqulay | ✅ |
| Asinxron (baza) | ❌ | ✅ MustAsync |
| Test | Qiyinroq | Oson |
| Paket | O'rnatilgan | NuGet |
[!TIP] Oddiy CRUD — Data Annotations yetarli. Murakkab formalar va qoidalar ko'p bo'lsa — FluentValidation. Bitta loyihada bitta usulni izchil tanlang.
Tez-tez uchraydigan xatolar
Validator ro'yxatdan o'tmagan — Unable to resolve service for type 'IValidator<BuyurtmaDto>'; AddValidatorsFromAssemblyContaining.
.When() ni noto'g'ri qatorga qo'yish — u faqat oldingi qoida(lar)ga taalluqli.
Validate (sinxron) bilan MustAsync — AsyncValidatorInvokedSynchronouslyException; ValidateAsync.
Xulosa
FluentValidation — qoidalar DTO dan alohida AbstractValidator<T> klassida: RuleFor(x => x.Maydon).NotEmpty().Matches(...).WithMessage(...). Shartli qoidalar — .When(), kolleksiyalar — RuleForEach + SetValidator, o'z sharti — Must, baza — MustAsync (lekin unique index baribir kerak).
Ro'yxatdan o'tkazish — AddValidatorsFromAssemblyContaining<Program>(); ishlatish — IValidator<T>.ValidateAsync va ValidationProblem (yoki umumiy filtr). Oson test qilinadi. Oddiy holatlar uchun Data Annotations ham yetarli.
Keyingi dars
48-dars: HTTP Status kodlar — 200, 201, 400, 401, 404, Ok(), BadRequest()