Ngay 4 thang 7, 2026 · Thoi gian doc: 5 phut
Trong mot du an truoc day, toi nhan ra mot van de lien quan den viec xac thuc du lieu: toi phai viet cung mot quy tac qua nhieu lan.
Hay xem endpoint tao don hang sau. Viec xac thuc xuat hien o ba noi:
- Data annotations tu dong xac thuc DTO cua request va tao schema JSON cho OpenAPI.
- Controller kiem tra cac quy tac khong the bieu dien trong OpenAPI, giup dung som truoc khi thuc hien truy van co so du lieu.
- Service chua logic nghiep vu van phai kiem tra lai, vi no co the duoc goi tu mot noi khac, chang han nhu mot background job sau khi lay entity tu co so du lieu.
Ba noi khac nhau co the de dang tao ra loi chi vi ban quen cap nhat mot trong so do.
public record CreateOrderRequest(
[EmailAddress] string CustomerEmail,
string ProductCode,
[Range(1, int.MaxValue)] int Quantity);
[HttpPost]
[ProducesResponseType<OrderResponse>(StatusCodes.Status200OK)]
[ProducesResponseType<ValidationProblemDetails>(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<OrderResponse>> Create(CreateOrderRequest request)
{
// Can phan tich lai, vi data annotations khong thay doi kieu du lieu.
if (!MailAddress.TryCreate(request.CustomerEmail, out _))
return ValidationProblem("Email cua khach hang khong hop le.");
try
{
var order = await orders.PlaceAsync(request.CustomerEmail, request.ProductCode, request.Quantity);
return Ok(OrderResponse.From(order));
}
catch (UnknownProductException)
{
return ValidationProblem("Khong co san pham nao co ma nay.");
}
catch (OutOfStockException)
{
return ValidationProblem("Khong du ton kho de hoan tat don hang.");
}
}
Data annotations xac thuc dia chi email, nhung gia tri ban van nhan duoc chi la mot string. Tuong tu, so nguyen duoc xac thuc la so duong, nhung ban khong the su dung truc tiep bao dam do trong kieu du lieu. Sau do, ta den service:
public async Task<Order> PlaceAsync(string customerEmail, string productCode, int quantity)
{
// Van phai kiem tra, du controller da kiem tra tat ca dieu nay.
if (string.IsNullOrWhiteSpace(productCode))
throw new ArgumentException("Ma san pham la bat buoc.", nameof(productCode));
if (quantity <= 0)
throw new ArgumentOutOfRangeException(nameof(quantity), "So luong phai la so duong.");
if (!await catalog.ContainsAsync(productCode))
throw new UnknownProductException(productCode);
// Xu ly nghiep vu that su.
}
Cuoi cung, PlaceAsync van phai dam bao moi thu hoat dong dung. No co the duoc goi tu controller, background worker hoac mot endpoint khac. Vi the, no phai xac thuc lai.
Mục lục
Hay phan tich, dung chi xac thuc
Giai phap nay den tu mot y tuong co dien cua lap trinh ham: thay vi kiem tra mot gia tri roi truyen cung mot kieu du lieu long leo qua cac tang, hay chuyen gia tri do mot lan duy nhat sang mot kieu khong the bieu dien trang thai khong hop le.
Mot int vua vuot qua dieu kien > 0 van chi la mot int. Bang chung ve viec no hop le bi mat ngay sau khi phep kiem tra thanh cong, vi vay phuong thuc tiep theo lai phai kiem tra tu dau. Trong khi do, Positive<int> luu giu bao dam do ngay trong kieu du lieu, va trinh bien dich se ap dung no cho moi noi su dung ve sau.
Do la ly do toi xay dung goi Kalicz.StrongTypes. Day la mot bo nho cac kieu C# nhu NonEmptyString, Email, NonEmptyEnumerable<T>, cung mot so kieu so hoc nhu Positive<T>, NonNegative<T> va cac kieu, tien ich huu ich khac.
Result<T, TError> la mot kieu rat huu ich, co the bieu dien trang thai thanh cong hoac loi cua thao tac. Ngoai ra, Maybe<T> giup ban bieu dien ba trang thai trong API. Vi du, khi cap nhat mot gia tri thanh mot gia tri moi, xoa gia tri ve rong, hoac khong cap nhat gia tri.
Thu vien con nhieu thanh phan khac. Ban co the xem ma nguon tai GitHub. Thu vien se tiep tuc duoc mo rong. Chang han, Interval<T> se giup bieu dien khoang bat dau va ket thuc, dong thoi xac thuc rang moc ket thuc nam sau moc bat dau.
Phan khong can viet them code
Chi can mot dong de cai dat:
dotnet add package Kalicz.StrongTypes
Sau do, ban co the bat dau su dung cac kieu nay:
public record CreateOrderRequest(
Email CustomerEmail,
NonEmptyString ProductCode,
Positive<int> Quantity);
[HttpPost]
[ProducesResponseType<OrderResponse>(StatusCodes.Status200OK)]
[ProducesResponseType<ValidationProblemDetails>(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<OrderResponse>> Create(CreateOrderRequest request)
{
// Khong can xac thuc tai day.
var result = await orders.PlaceAsync(request.CustomerEmail, request.ProductCode, request.Quantity);
if (result.Error is { } error)
return error switch
{
PlaceOrderError.UnknownProduct => FieldProblem(nameof(request.ProductCode), "Khong co san pham nao co ma nay."),
PlaceOrderError.OutOfStock => FieldProblem(nameof(request.Quantity), "Khong du ton kho de hoan tat don hang."),
};
return Ok(OrderResponse.From(result.Success!));
}
private ActionResult FieldProblem(string field, string message)
{
ModelState.AddModelError(field, message);
return ValidationProblem(ModelState);
}
Data annotations da bien mat. Viec chuyen doi sang MailAddress cung khong con. Moi thong tin ve gia tri gio day nam trong cac kieu du lieu. Vi phuong thuc tra ve mot ket qua thay vi nem ngoai le, trinh bien dich biet chinh xac nhung loi nao co the xay ra.
Tat ca cac kieu deu co the tu dong chuyen doi qua lai voi JSON. Schema OpenAPI cung duoc cap nhat dung. Hay cai dat goi Kalicz.StrongTypes.OpenApi.Swashbuckle hoac phien ban danh cho Microsoft.AspNetCore.OpenApi, sau do dang ky nhu sau:
// Program.cs - cai dat Kalicz.StrongTypes.OpenApi.Swashbuckle, sau do goi mot lan
builder.Services.AddSwaggerGen(options => options.AddStrongTypes());
Request o tren se tao ra dung cac rang buoc ma client can:
{
"customerEmail": { "type": "string", "format": "email", "minLength": 1, "maxLength": 254 },
"productCode": { "type": "string", "minLength": 1 },
"quantity": { "type": "integer", "format": "int32", "minimum": 0, "exclusiveMinimum": true }
}
Chu ky ham tro thanh tai lieu
Phan tuyet voi nhat la cach tiep can nay giup code de hieu hon nhieu. Moi phuong thuc co the neu ro cac yeu cau that su:
// Truoc day: moi noi goi phai kiem tra lai, moi nguoi doc deu phai doan.
Task<Order> PlaceAsync(string email, string code, int quantity);
// Sau do: chu ky ham la hop dong.
Task<Result<Order, PlaceOrderError>> PlaceAsync(Email email, NonEmptyString code, Positive<int> quantity);
Chu ky thu hai tra loi cac cau hoi ma chu ky dau tien buoc ban phai tim hieu: ma san pham khong the rong, so luong khong the bang khong. Nguoi review khong con phai hoi “neu so luong la am ba thi sao?”, vi cau hoi do khong con phu hop voi kieu du lieu.
Cac quy tac nghiep vu co the that bai mot cach hop le cung duoc xu ly bang Result<T, TError>. Loi tro thanh mot gia tri trong chu ky ham thay vi mot ngoai le bat ngo:
public async Task<Result<Order, PlaceOrderError>> PlaceAsync(Email email, NonEmptyString code, Positive<int> quantity)
{
if (!await catalog.ContainsAsync(code))
return PlaceOrderError.UnknownProduct;
if (!await inventory.HasStockAsync(code, quantity))
return PlaceOrderError.OutOfStock;
return new Order(email, code, quantity);
}
Quan trong hon, khi doc lai code sau nam thang, ban khong can nho lai boi canh. Ban cung khong can tim hieu gia tri den tu dau va co the chua nhung gi. Nhan vien moi cung khong phai lien tuc hoi nguoi khac. Code tu mo ta chinh xac dieu dang dien ra, mang lai ro rang va boi canh.
Cac kieu du lieu di thang den co so du lieu
Khi nap mot entity vao context bang EF Core, ban thuong phai xac thuc lai. Tuy nhien, voi goi Kalicz.StrongTypes.EfCore, cac kieu manh co the duoc su dung truc tiep lam thuoc tinh cua entity. Mot lenh trong options builder se gan value converter cho tung kieu:
public sealed class Order
{
public Guid Id { get; init; }
public Email CustomerEmail { get; init; }
public NonEmptyString ProductCode { get; init; }
public Positive<int> Quantity { get; init; }
}
// Program.cs - dang ky StrongTypes cho db context.
services.AddDbContext<ShopDbContext>(options => options
.UseNpgsql(connectionString)
.UseStrongTypes());
Cot trong co so du lieu van la varchar hoac int thong thuong, khong co thay doi nao ve cach luu tru. Tuy nhien, EF Core se khong cho phep luu cac gia tri khong hop le. Neu ban co gang nap gia tri khong hop le tu co so du lieu, ung dung se loi ro rang thay vi am tham tra ve null. Thong tin da duoc phan tich vi the duoc giu nguyen lau dai. Ban co the nap entity va goi PlaceAsync mot cach an toan.
It truong hop dac biet hon, it test hon
So luong unit test can thiet giam di dang ke. Nhieu truong hop bien khong con kha thi. Ban khong the truyen null, chuoi rong hoac chuoi chi gom khoang trang vao NonEmptyString. Ban cung khong the truyen -1 vao so luong. Nhung gi con lai la cac test ve hanh vi, tuc nhung test thuc su co y nghia.
API de tao cac kieu nay cung the hien ro y dinh:
NonEmptyString name = NonEmptyString.Create(input); // nem ngoai le neu khong hop le
NonEmptyString? safe = NonEmptyString.TryCreate(input); // tra ve null neu khong hop le
NonEmptyString? fluent = input.AsNonEmpty(); // extension tuong tu
Voi cac thuoc tinh ban van muon kiem tra, property-based testing cho phep ban viet mot phuong thuc test va chay no voi hang tram gia tri khac nhau. Goi Kalicz.StrongTypes.FsCheck tao ra cac gia tri manh hop le cho property-based test, vi the generator cung ton trong cac bat bien ma code cua ban su dung.
Hay thu ngay
Toan bo su thay doi co the tom tat nhu sau: truoc day, moi tang lap lai viec xac thuc cung mot chuoi gia tri; sau do, gia tri duoc phan tich mot lan tai bien JSON va NonEmptyString mang theo bao dam qua controller, service va co so du lieu.
Ngay ca phan backend cua chinh website nay cung chay tren StrongTypes: request DTO, domain event va trang thai entity. Neu cach tiep can nay phu hop voi ban, trang StrongTypes co them so do va vi du. Ma nguon nam tren GitHub, con goi thu vien co tren NuGet.
Hay bat dau voi mot request record. Xoa cac doan kiem tra da tro nen thua. Chi sau mot buoi lam viec, ban se nhan ra minh muon ap dung cach nay o moi noi.



