Validation — Data Annotations, ModelState, [Required], [Range]
Muammodan boshlaylik
[HttpPost]
public async Task<IActionResult> Yarat(MahsulotYaratDto dto)
{
if (string.IsNullOrWhiteSpace(dto.Nomi)) return BadRequest("Nom majburiy");
if (dto.Nomi.Length > 200) return BadRequest("Nom juda uzun");
if (dto.Narx <= 0) return BadRequest("Narx musbat bo'lsin");
if (dto.Qoldiq < 0) return BadRequest("Qoldiq manfiy bo'lmasin");
// ... va har bir endpoint da shunday
}
Har bir action boshida o'nlab if. Xatolar birma-bir qaytariladi — mijoz birinchisini tuzatadi, keyingisini oladi, va hokazo. Format har joyda boshqacha.
Data Annotations
Qoidalar — DTO property lariga atribut sifatida (34-dars):
using System.ComponentModel.DataAnnotations;
public record MahsulotYaratDto
{
[Required(ErrorMessage = "Nom majburiy")]
[StringLength(200, MinimumLength = 2, ErrorMessage = "Nom 2–200 belgi")]
public string Nomi { get; init; } = "";
[Range(0.01, 1_000_000_000, ErrorMessage = "Narx musbat bo'lsin")]
public decimal Narx { get; init; }
[Range(0, 100_000)]
public int Qoldiq { get; init; }
[Required, Url]
public string? RasmUrl { get; init; }
}
[HttpPost]
public async Task<IActionResult> Yarat(MahsulotYaratDto dto) // tekshiruvlar yo'q!
{
var m = await service.YaratAsync(dto);
return CreatedAtAction(nameof(Ol), new { id = m.Id }, m);
}
[ApiController] model binding dan keyin validatsiyani avtomatik bajaradi. Xato bo'lsa — action umuman chaqirilmaydi, va 400 qaytadi.
Hayotiy o'xshatma
Aeroportdagi ro'yxatdan o'tish. Har bir yo'lovchini kassir uyda o'zicha tekshirmaydi — kirishda bitta standart tekshiruv: pasport bormi, chipta to'g'rimi, yuk og'irligi chegaradami. Muammo bo'lsa — yo'lovchiga barcha kamchiliklar ro'yxati bir marta aytiladi, samolyotga esa faqat tekshiruvdan o'tganlar chiqadi.
Avtomatik javob — ProblemDetails
POST /api/mahsulotlar
{ "nomi": "A", "narx": -5, "qoldiq": 10 }
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Nomi": ["Nom 2–200 belgi"],
"Narx": ["Narx musbat bo'lsin"],
"RasmUrl": ["The RasmUrl field is required."]
},
"traceId": "00-4bf92f..."
}
Barcha xatolar birdaniga, maydon bo'yicha guruhlangan, standart formatda (RFC 7807/9457). Frontend har bir xatoni tegishli forma maydoni ostida ko'rsata oladi.
Asosiy atributlar
| Atribut | Tekshiradi |
|---|---|
[Required] |
Bo'sh emas (null, "") |
[StringLength(max, MinimumLength = min)] |
String uzunligi |
[MinLength], [MaxLength] |
String yoki kolleksiya uzunligi |
[Range(min, max)] |
Son oralig'i |
[EmailAddress] |
Email formati |
[Phone], [Url], [CreditCard] |
Formatlar |
[RegularExpression(@"...")] |
Regex ga moslik |
[Compare("Parol")] |
Boshqa property ga tengligi (parolni takrorlash) |
[AllowedValues("a", "b")] (.NET 8+) |
Ruxsat etilgan qiymatlar |
[Length(min, max)] (.NET 8+) |
Uzunlik oralig'i |
public record RoyxatDto
{
[Required, EmailAddress]
public string Email { get; init; } = "";
[Required, MinLength(8)]
[RegularExpression(@"^(?=.*[A-Za-z])(?=.*\d).+quot;, ErrorMessage = "Parolda harf va raqam bo'lsin")]
public string Parol { get; init; } = "";
[Compare(nameof(Parol), ErrorMessage = "Parollar mos emas")]
public string ParolTakror { get; init; } = "";
[RegularExpression(@"^\+998\d{9}quot;, ErrorMessage = "Telefon +998XXXXXXXXX formatida")]
public string? Telefon { get; init; }
}
Nullable va [Required]
Nullable reference types (14-dars) yoqilgan bo'lsa, string Nomi (nullable emas) avtomatik majburiy hisoblanadi; string? Telefon — ixtiyoriy.
[!INFO]
[Required]qiymat turlari uchun (int,decimal) ishlamaydi — ular hech qachonnullemas, JSON da yo'q bo'lsa0bo'ladi. "Narx yuborilganmi?" ni tekshirish uchundecimal?+[Required]yoki[Range]bilan0ni rad eting.
IValidatableObject — bir nechta maydon
public record AksiyaDto : IValidatableObject
{
public DateTime Boshlanish { get; init; }
public DateTime Tugash { get; init; }
[Range(1, 90)] public int Foiz { get; init; }
public IEnumerable<ValidationResult> Validate(ValidationContext ctx)
{
if (Tugash <= Boshlanish)
yield return new ValidationResult("Tugash sanasi boshlanishdan keyin bo'lsin", [nameof(Tugash)]);
if (Boshlanish < DateTime.Today)
yield return new ValidationResult("O'tgan sana bo'lmaydi", [nameof(Boshlanish)]);
}
}
Custom atribut — 34-dars (ValidationAttribute).
ModelState — qo'lda
[HttpPost]
public async Task<IActionResult> Yarat(MahsulotYaratDto dto)
{
if (await service.NomBandmiAsync(dto.Nomi))
{
ModelState.AddModelError(nameof(dto.Nomi), "Bu nomli mahsulot allaqachon bor");
return ValidationProblem(ModelState); // xuddi shu formatda 400
}
// ...
}
Bazaga murojaat qiladigan tekshiruvlar (noyoblik) — servis yoki action ichida, lekin javob formati bir xil.
[ApiController] siz if (!ModelState.IsValid) return BadRequest(ModelState); ni har bir action da qo'lda yozish kerak bo'lardi.
[!TIP] Validatsiyaning ikki darajasi: kiruvchi ma'lumot shakli (format, uzunlik, oraliq) — DTO atributlarida yoki FluentValidation da; biznes qoidalari (qoldiq yetarlimi, foydalanuvchi bloklanmaganmi) — servis qatlamida. Atributlarga bazaga murojaat va biznes mantiq qo'shmang.
Tez-tez uchraydigan xatolar
Atributlar entity da, DTO da emas — API kirishi tekshirilmaydi.
[Required] int da — hech qachon ishlamaydi; int? yoki [Range].
[ApiController] siz avtomatik validatsiyani kutish — ModelState.IsValid ni tekshirish kerak.
Xulosa
Data Annotations — validatsiya qoidalari DTO property larida: [Required], [StringLength], [Range], [EmailAddress], [RegularExpression], [Compare]. [ApiController] ularni avtomatik tekshiradi; xato bo'lsa action chaqirilmaydi va 400 ProblemDetails — barcha xatolar maydon bo'yicha.
Nullable bo'lmagan reference turlar — avtomatik majburiy; qiymat turlari uchun [Required] ishlamaydi. Bir nechta maydonli qoidalar — IValidatableObject; qo'lda — ModelState.AddModelError + ValidationProblem. Biznes qoidalari — servisda.
Keyingi dars
47-dars: FluentValidation — AbstractValidator, kuchli validatsiya qoidalari