Khi xây dựng API trong ASP.NET Core, định tuyến (routing) đóng vai trò quan trọng trong việc ánh xạ các yêu cầu HTTP tới các endpoint. Ràng buộc tuyến đường (route constraints) cho phép bạn xác thực và định hình các tham số tuyến đường ngay trong mẫu URL (URL pattern). Điều này đảm bảo chỉ những giá trị khớp với định dạng mong đợi mới được truyền vào endpoint của bạn.
Trong hướng dẫn này, chúng ta sẽ đi qua các ràng buộc tuyến đường có sẵn trong ASP.NET Core Web APIs. Chúng ta sẽ dùng Minimal APIs cho các ví dụ, nhưng các khái niệm tương tự cũng áp dụng khi làm việc với controllers.
Mục lục
Ràng buộc Integer, boolean và GUID
Ràng buộc tuyến đường có thể ép kiểu tham số như int, bool, hoặc Guid. Ví dụ:
// Program.cs
app.MapGet("product/{id:int:required}/active/is-{active:bool}", (int id, bool active)
=> new { id, active });
app.MapGet("product/identifier/{id:guid}", (Guid id)
=> new { id });
Hãy chú ý cách có thể thêm nhiều ràng buộc vào một tham số bằng cách nối chúng với dấu hai chấm (:). Ví dụ, {id:int:required} ép buộc rằng id phải là một số nguyên hợp lệ và là bắt buộc.
Nếu bạn muốn xem các ràng buộc tuyến đường được sử dụng trong bối cảnh thực tế, khóa học Minimal APIs for complete beginners của chúng tôi hướng dẫn xây dựng các API ASP.NET Core thực tế từ đầu. Khóa học bao gồm các ràng buộc tuyến đường phổ biến – như int – và chỉ ra cách thức cũng như lý do chúng được áp dụng trong thiết kế API hàng ngày.
Các kiểu số và ngày khác
ASP.NET Core cũng hỗ trợ các ràng buộc cho các kiểu phổ biến khác như DateTime, decimal, double, float, và long.
// Program.cs
app.MapGet("products/released/{dateReleased:datetime}", (DateTime dateReleased)
=> new { dateReleased });
app.MapGet("products/under-{price:decimal}", (decimal price)
=> new { price });
app.MapGet("products/over-{price:double}", (double price)
=> new { price });
app.MapGet("products/exactly-{price:float}", (float price)
=> new { price });
app.MapGet("products/from-{price:long}", (long price)
=> new { price });
Điều này giúp dễ dàng ép buộc đúng kiểu dữ liệu ở cấp độ định tuyến trước khi chúng chạm tới logic của bạn.
Alpha, độ dài tối thiểu và tối đa
Các ràng buộc cũng có thể xác thực tham số chuỗi, đảm bảo chúng chỉ chứa các ký tự chữ cái hoặc nằm trong các giới hạn độ dài nhất định.
// Program.cs
app.MapGet("product/{slug:alpha:minlength(4):maxlength(8)}", (string slug)
=> new { slug });
app.MapGet("product/{slug:alpha:length(8)}/features", (string slug)
=> new { slug });
app.MapGet("product/{slug:alpha:length(4,8)}/spec", (string slug)
=> new { slug });
Ràng buộc alpha đảm bảo chỉ các ký tự chữ cái được sử dụng. Ràng buộc minlength đảm bảo tham số có số ký tự tối thiểu, trong khi ràng buộc maxlength làm điều ngược lại.
Với ràng buộc length:
- Nếu bạn cung cấp một tham số trong ràng buộc, điều đó có nghĩa là tham số tuyến đường phải có đúng số ký tự được chỉ định. Ví dụ:
length(8)nghĩa là tham số tuyến đường phải có 8 ký tự. - Nếu bạn cung cấp hai tham số, nó sẽ kết hợp
minlengthvàmaxlengthlại với nhau. Ví dụ:length(8,16)nghĩa là tham số tuyến đường phải có từ 8 đến 16 ký tự.
Ràng buộc phạm vi (Range)
Ràng buộc range cho phép bạn ép buộc giá trị tối thiểu và tối đa cho số nguyên.
// Program.cs
app.MapGet("product/code/{code:int:range(1,9999)}",
(int code, int page) =>
new { code, page });
app.MapGet("product/code/{code:int:min(1):max(9999)}/features",
(int code, int page) =>
new { code, page });
Nếu bạn muốn chỉ định giá trị tối thiểu hoặc tối đa của một số nguyên trong tham số tuyến đường, bạn có thể dùng ràng buộc min hoặc max.
Ràng buộc Regex
Để có sự linh hoạt tối đa, bạn có thể sử dụng biểu thức chính quy (regular expressions) để định nghĩa các ràng buộc tuyến đường.
// Program.cs
app.MapGet("product/sku/{sku:regex(^[A-Za-z]{{0,3}}\\d{{1}}$)}", (string sku) =>
new { sku });
Trong ví dụ này, tham số sku phải bắt đầu bằng tối đa ba chữ cái, theo sau là một chữ số duy nhất.
Ràng buộc file và nonfile
Nếu endpoint của bạn xử lý tệp tin, bạn có thể dùng các ràng buộc file và nonfile để phân biệt giữa các đường dẫn dạng thư mục và tên tệp tin.
// Program.cs
app.MapGet("product/files/{directory:nonfile}/{filename:file}",
(string directory, string filename) =>
new { directory, filename });
Với các ràng buộc tuyến đường này:
filecó thể chứa các phân đoạn đường dẫn, nhưng phân đoạn cuối cùng của nó phải có dấu chấm (.) và được theo sau bởi một hoặc nhiều ký tự không phải dấu chấm.nonfilekhông được có dấu chấm trong phân đoạn đường dẫn cuối cùng của nó.
Khớp tuyến đường (Matching routes)
Khi làm việc với Minimal APIs, có thể có nhiều mẫu tuyến đường khớp với cùng một yêu cầu đến. Hãy xem xét hai tuyến đường sau:
// Program.cs
app.MapGet("product/{id:regex(\\d{{5}})}", (int id) =>
new { response = "First route" });
app.MapGet("product/{id:int}", (int id) =>
new { response = "Second route" });
Cả hai tuyến đường đều chấp nhận giá trị số. Tuyến đường đầu tiên dùng ràng buộc regex để khớp chính xác năm chữ số, trong khi tuyến đường thứ hai dùng ràng buộc int có sẵn, cũng chấp nhận các số có năm chữ số. Vì cả hai tuyến đường đều có thể xử lý product/12345, ASP.NET Core không thể xác định tuyến đường nào nên được sử dụng.
Khi điều này xảy ra, bạn sẽ nhận được lỗi sau:
AmbiguousMatchException: The request matched multiple endpoints
Để giải quyết, bạn có thể chỉ định thứ tự thực thi rõ ràng bằng .WithOrder(). Các tuyến đường có số thấp hơn có độ ưu tiên cao hơn.
// Program.cs
app.MapGet("product/{id:regex(\\d{{5}})}", (int id) =>
new { response = "First route" })
.WithOrder(1);
app.MapGet("product/{id:int}", (int id) =>
new { response = "Second route" })
.WithOrder(2);
Với thứ tự được áp dụng:
- ID có 5 chữ số sẽ khớp với tuyến đường đầu tiên sử dụng ràng buộc
regex - Bất kỳ ID số nguyên nào khác sẽ khớp với tuyến đường thứ hai sử dụng ràng buộc
int
Tạo ràng buộc tuyến đường tùy chỉnh
ASP.NET Core cung cấp nhiều ràng buộc tuyến đường có sẵn, nhưng đôi khi bạn cần thứ gì đó cụ thể hơn. Ví dụ, giả sử bạn muốn có một ràng buộc chuyên dụng để xác thực mã ZIP của Hoa Kỳ (chính xác 5 chữ số).
Bạn có thể tạo ràng buộc của riêng mình bằng cách triển khai IRouteConstraint.
// ZipCodeConstraint.cs
public class ZipCodeConstraint : IRouteConstraint
{
public bool Match(
HttpContext? httpContext,
IRouter? route,
string routeKey,
RouteValueDictionary values,
RouteDirection routeDirection)
{
if (!values.TryGetValue(routeKey, out var routeValue))
{
return false;
}
return new Regex("^[0-9]{5}$", RegexOptions.CultureInvariant)
.IsMatch(routeValue?.ToString() ?? string.Empty);
}
}
Đăng ký ràng buộc
Thêm ràng buộc tùy chỉnh vào hệ thống tuyến đường trong Program.cs:
// Program.cs
builder.Services.Configure<RouteOptions>(routeOptions =>
{
routeOptions.ConstraintMap.Add("zipcode", typeof(ZipCodeConstraint));
});
Thêm nó bằng khóa zipcode có nghĩa là chúng ta có thể sử dụng zipcode làm ràng buộc.
Sử dụng ràng buộc trong route Minimal API
Sau khi đăng ký, hãy sử dụng nó giống như bất kỳ ràng buộc có sẵn nào:
// Program.cs
app.MapGet("/zip-code/{zip:zipcode}", (string zip) =>
new { zip });
Bây giờ tuyến đường chỉ khớp nếu tham số {zip} có đúng năm chữ số.
Xem video
Khi xem video, bạn có thể thấy từng ràng buộc tuyến đường này hoạt động trong thực tế.
Nếu ràng buộc tuyến đường không khớp
Các ràng buộc tuyến đường trong ASP.NET Core Web APIs cung cấp một cách mạnh mẽ để xác thực đầu vào ngay trong mẫu URL.
Nếu một mẫu URL không thể khớp với một tuyến đường, nó sẽ trả về mã trạng thái 404 Not Found cho phản hồi.
Sử dụng đúng ràng buộc tuyến đường giúp giảm mã xác thực lặp lại (boilerplate), mang lại giải pháp sạch sẽ hơn.



