Có câu cửa miệng mà anh em dev .NET ai cũng thuộc lòng: "Middleware như cổng soát vé". Đúng, nhưng tại sao các hệ thống lớn lại cần và phụ thuộc vào nó, ta cần nhìn sâu hơn chút.
Trước khi mổ xẻ, ôn lại chút khái niệm cho những ai mới làm quen nha.
Khái niệm "Củ hành tây và trạm gác"
Hãy tưởng tượng Web API giống như một tòa nhà có lõi là phòng Giám đốc (Controller). Để gặp được Giám đốc, một vị khách (Request) phải đi qua cổng bảo vệ (kiểm tra thẻ), qua quầy lễ tân (lấy thông tin), lúc này mới được vô phòng. Khi Giám đốc đưa giấy tờ (Response) cho khách và đi về, vị khách phải đi ngược lại qua lễ tân, qua cổng bảo vệ rồi mới ra ngoài.
Middleware chính là những "trạm gác" đó. Trong ASP.NET Core, các trạm gác này xếp thành một đường ống (pipeline). Request đi xuyên từng lớp, đập vào Controller, rồi Response lại dội ngược ra qua chính những lớp đó. Người ta gọi đây là mô hình "Củ hành tây" (Onion architecture cho HTTP Request).
Tại sao lại cần Middleware?
Nếu thấy Middleware chưa quan trọng, thử tưởng tượng nếu không có nó.
Giả sử có một API lấy thông tin User. Trong đó:
- Ghi log xem request tốn bao nhiêu mili-giây.
- Kiểm tra xem User có mang theo Token hợp lệ không.
- Try-Catch nếu code lỗi trả về HTTP 500 thay vì sập nguồn.
Nếu không có Middleware, cái Controller sẽ như một bãi rác như bên dưới:
[HttpGet("{id}")]
public IActionResult GetUser(int id)
{
var stopwatch = Stopwatch.StartNew(); // 1. Log time
try
{
if (!Request.Headers.ContainsKey("Authorization")) // 2. Check auth
return Unauthorized();
// ... Logic thực sự lấy User ở đây ... (Business Logic)
}
catch (Exception ex) // 3. Catch error
{
return StatusCode(500, "Lỗi rồi");
}
finally
{
Log.Info($"Tốn {stopwatch.ElapsedMilliseconds}ms");
}
}
Khi có 100 API, phải copy-paste cái cục code "bầy hầy" này 100 lần.
Đây là lúc Middleware cứu lập trình viên bằng khái niệm Cross-cutting Concerns (Các mối quan tâm cắt ngang). Logic nghiệp vụ (lấy data User) và logic vòng ngoài (log, auth, error handling) sẽ tách bạch. Controller chỉ làm đúng một việc: Nhận data, xử lý logic. Mọi thứ râu ria, Middleware lo.
Chuyện gì xảy ra bên dưới?
Dưới góc nhìn kiến trúc, "Pipeline" thực chất được build lên từ khái niệm nào trong C#?
Câu trả lời: RequestDelegate và Design Pattern Chain of Responsibility.
Khi ứng dụng khởi động (hàm Program.cs), .NET không chạy ngay các Middleware. Nó chỉ làm một việc, "đăng ký" và móc nối nhau thành một chuỗi các Delegate (hàm trỏ đến hàm).
Mỗi Middleware bản chất là một khối code nhận vào 2 tham số chính:
- HttpContext: Chứa toàn bộ thông tin của Request và Response.
- RequestDelegate next: Một con trỏ chỉ thẳng đến cái Middleware tiếp theo trong hàng đợi.
Khi có 1 HTTP Request bay tới server, Kestrel (web server của .NET) sẽ đóng gói thành object HttpContext và đẩy vào đầu chuỗi Delegate này.
Từ khóa sinh tử: await next()
Sự kì diệu Middleware nằm ở đúng 1 dòng code: await next(context); Đây là mấu chốt chia luồng chạy làm 2 nửa:
- Inbound (Trước await next()): Code chạy khi Request đang trên đường đi vào. Ví dụ đọc Headers, kiểm tra Token, đo giờ,..
- Điểm chuyển giao: Chữ await next() sẽ gọi Middleware tiếp theo đứng sau nó, và cứ thế gọi dây chuyền đến khi chạm Controller (điểm cuối cùng ống).
- Outbound (Sau await next()): Code chạy khi Controller đã xử lý xong và Response đang trên đường dội ngược ra, ví dụ ghi log tổng thời gian, biến đổi body của Response.
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
// 1. CHẠY LÚC ĐI VÀO (Inbound)
Console.WriteLine("Request đi vào nè...");
// 2. CHUYỂN GIAO CHO THẰNG TIẾP THEO
await next(context);
// 3. CHẠY LÚC ĐI RA (Outbound)
Console.WriteLine("Response đi ra nè...");
}
Vì là chuỗi nối tiếp, thứ tự đăng ký Middleware cực kì quan trọng. Ví dụ phải chạy UseAuthentication (xác thực mày là ai) trước UseAuthorization (mày có quyền làm gì). Đảo ngược hệ thống sẽ ra đê.
Kết luận
Hiểu bản chất của chuỗi Delegate và luồng Inbound/Outbound sẽ nhận ra Middleware cực kì mạnh mẽ. Nhưng sức mạnh thường đi kèm rủi ro.
No comments yet. Be the first to comment!