Middleware trong ASP.NET Core
Nguồn: ASP.NET Core Middleware
Middleware (phần mềm trung gian) là phần mềm được lắp ráp vào pipeline (đường ống xử lý) của ứng dụng để xử lý các request (yêu cầu) và response (phản hồi). Mỗi middleware:
- Chọn có truyền request sang middleware tiếp theo trong pipeline hay không.
- Có thể thực hiện công việc trước và sau middleware tiếp theo trong pipeline.
Request delegate được sử dụng để build request pipeline. Các request delegate xử lý mỗi HTTP request.
Request delegate được cấu hình bằng cách sử dụng các extension method (phương thức mở rộng) Run, Map, và Use. Một request delegate riêng lẻ có thể được chỉ định inline (nội tuyến) như một anonymous method (phương thức ẩn danh) (được gọi là inline middleware) hoặc được định nghĩa trong một class có thể tái sử dụng. Các anonymous method inline hoặc class có thể tái sử dụng này được gọi là middleware hoặc middleware components (thành phần middleware). Mỗi middleware trong request pipeline chịu trách nhiệm gọi middleware tiếp theo trong pipeline hoặc short-circuit (ngắn mạch) pipeline. Khi một middleware short-circuit, nó được gọi là terminal middleware (middleware đầu cuối) vì nó ngăn chặn các middleware tiếp theo xử lý request.
Tạo middleware pipeline với WebApplication
ASP.NET Core request pipeline bao gồm một chuỗi các request delegate, được gọi lần lượt. Sơ đồ sau minh họa khái niệm này. Luồng thực thi theo các mũi tên đen.
Mỗi delegate có thể thực hiện các thao tác trước và sau delegate tiếp theo. Các delegate xử lý exception (ngoại lệ) nên được gọi sớm trong pipeline, để chúng có thể bắt các exception xảy ra ở các giai đoạn sau của pipeline.
Ứng dụng ASP.NET Core đơn giản nhất thiết lập một terminal middleware duy nhất là một anonymous function request delegate để xử lý các request mà không cần request pipeline.
Trong ví dụ sau:
- Lời gọi RunExtensions.Run được gọi trên mỗi request và ghi "Hello world!" vào response.
- Lời gọi WebApplication.Run ở cuối code block chạy ứng dụng và block (chặn) calling thread (luồng gọi) cho đến khi host shutdown (tắt).
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Run(async context =>
{
await context.Response.WriteAsync("Hello world!");
});
app.Run();Phản hồi khi truy cập ứng dụng trong trình duyệt tại URL khởi chạy:
Hello world!
Kết nối nhiều request delegate với nhau bằng Use. Tham số next đại diện cho delegate tiếp theo trong pipeline. Bạn thường có thể thực hiện các hành động cả trước và sau delegate next.
Ví dụ sau minh họa:
- Hai lần gọi Use, mỗi lần ghi vào console:
- Nơi có thể thực hiện công việc có thể ghi vào response (
context.Response, HttpResponse). - Nơi có thể thực hiện công việc không ghi vào response sau khi tham số
nextđược gọi. - Một terminal request delegate với lời gọi RunExtensions.Run ghi "Hello world!" vào response.
- Một lời gọi Use cuối cùng, không bao giờ được thực thi vì nó đến sau terminal request delegate Run.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Use(async (context, next) =>
{
Console.WriteLine("Work that can write to the response. (1)");
await next.Invoke(context);
Console.WriteLine("Work that doesn't write to the response. (1)");
});
app.Use(async (context, next) =>
{
Console.WriteLine("Work that can write to the response. (2)");
await next.Invoke(context);
Console.WriteLine("Work that doesn't write to the response. (2)");
});
app.Run(async context =>
{
await context.Response.WriteAsync("Hello world!");
});
app.Use(async (context, next) =>
{
Console.WriteLine("This statement isn't reached. (3)");
await next.Invoke(context);
Console.WriteLine("This statement isn't reached. (3)");
});
app.Run();Trong cửa sổ console của ứng dụng khi chạy ứng dụng:
Work that can write to the response. (1) Work that can write to the response. (2) Work that doesn't write to the response. (2) Work that doesn't write to the response. (1)
Short-circuiting (ngắn mạch) request pipeline thường mong muốn vì nó tránh công việc không cần thiết. Ví dụ, Static File Middleware có thể hoạt động như terminal middleware bằng cách xử lý request cho một file tĩnh và ngắn mạch phần còn lại của pipeline. Middleware được thêm vào pipeline trước terminal middleware vẫn xử lý code sau câu lệnh next.Invoke của chúng. Nếu bạn không có kế hoạch gọi next.Invoke vì mục tiêu của bạn là kết thúc pipeline, hãy sử dụng delegate Run thay vì gọi extension method Use.
Không gọi next.Invoke trong khi hoặc sau khi response đã được gửi đến client. Sau khi HttpResponse đã bắt đầu, các thay đổi dẫn đến exception. Ví dụ, đặt header hoặc status code sẽ ném exception sau khi response bắt đầu. Ghi vào response body sau khi gọi next có thể:
- Gây ra vi phạm giao thức, chẳng hạn như ghi nhiều bytes hơn giá trị
Content-Lengthheader đã nêu. - Làm hỏng định dạng body, chẳng hạn như ghi HTML footer vào CSS file.
Để xác định xem response đã bắt đầu hay chưa, kiểm tra giá trị của HasStarted.
Delegate Run
Delegate Run không nhận tham số next. Delegate Run đầu tiên luôn kết thúc pipeline. Run là một convention (quy ước), và một số middleware có thể expose các phương thức Run thực thi ở cuối pipeline.
Bất kỳ delegate Use hoặc Run nào sau delegate Run đầu tiên đều không được gọi.
Phân nhánh middleware pipeline
Extension Map được sử dụng như một convention để phân nhánh pipeline. Map phân nhánh request pipeline dựa trên các kết quả khớp với request path (đường dẫn request) đã cho. Nếu request path bắt đầu bằng path đã cho, nhánh đó được thực thi.
Trong ví dụ sau, HandleMap1 được gọi cho các request đến /map1, và HandleMap2 được gọi cho các request đến /map2:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Map("/map1", HandleMap1);
app.Map("/map2", HandleMap2);
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from the non-Map delegate!");
});
app.Run();
private static void HandleMap1(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map 1");
});
}
private static void HandleMap2(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map 2");
});
}Bảng sau đây hiển thị các request và response khi sử dụng code trước đó.
| Request | Response |
|---|---|
/ | Hello from the non-Map delegate. |
/map1 | Map 1 |
/map2 | Map 2 |
/map3 | Hello from the non-Map delegate. |
Khi Map được sử dụng, các path segment (đoạn đường dẫn) khớp được xóa khỏi HttpRequest.Path và được thêm vào HttpRequest.PathBase cho mỗi request.
MapWhen phân nhánh request pipeline dựa trên kết quả của predicate (vị từ) đã cho. Bất kỳ predicate nào có kiểu Func<HttpContext, bool> đều có thể được sử dụng để map request đến một nhánh mới của pipeline. Trong ví dụ sau, một predicate được sử dụng để phát hiện sự hiện diện của biến query string (chuỗi truy vấn) có tên là "branch":
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapWhen(context => context.Request.Query.ContainsKey("branch"), HandleBranch);
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from the non-Map delegate.");
});
app.Run();
private static void HandleBranch(IApplicationBuilder app)
{
app.Run(async context =>
{
var branchVer = context.Request.Query["branch"];
await context.Response.WriteAsync($"Branch used = '{branchVer}'");
});
}UseWhen có thể phân nhánh request pipeline dựa trên kết quả của predicate đã cho. Khác với MapWhen, nhánh này được nối lại với main pipeline (pipeline chính) nếu nó không chứa terminal middleware:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
appBuilder => HandleBranchAndRejoin(appBuilder));
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from the non-Map delegate.");
});
app.Run();
void HandleBranchAndRejoin(IApplicationBuilder app)
{
var logger = app.ApplicationServices.GetRequiredService<ILogger<Program>>();
app.Use(async (context, next) =>
{
var branchVer = context.Request.Query["branch"];
logger.LogInformation("Branch used = {branchVer}", branchVer.ToString());
Console.WriteLine("Work that can write to the response.");
await next.Invoke(context);
Console.WriteLine("Work that doesn't write to the response.");
});
}Thứ tự middleware
Thứ tự mà middleware xuất hiện trong file Program.cs của ứng dụng xác định thứ tự mà middleware được gọi trên một request với thứ tự ngược lại cho response.
Sơ đồ sau đây cho thấy request processing pipeline (pipeline xử lý request) đầy đủ cho các ứng dụng ASP.NET Core MVC và Razor Pages.
Thứ tự mà các middleware component được thêm vào trong file Program.cs xác định thứ tự mà các middleware component được gọi trên các request và thứ tự ngược lại cho response. Thứ tự này quan trọng đối với bảo mật, hiệu suất và chức năng.
Code sau trong Program.cs thêm các middleware component liên quan đến bảo mật theo thứ tự điển hình được khuyến nghị:
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebMiddleware.Data;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
// app.UseCookiePolicy();
app.UseRouting();
// app.UseRateLimiter();
// app.UseRequestLocalization();
// app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
// app.UseSession();
// app.UseResponseCompression();
// app.UseResponseCaching();
app.MapRazorPages();
app.MapDefaultControllerRoute();
app.Run();Trong code trên:
UseCors,UseAuthentication, vàUseAuthorizationphải xuất hiện theo thứ tự được hiển thị.UseCorshiện tại phải xuất hiện trướcUseResponseCaching. Yêu cầu này được giải thích trong GitHub issue dotnet/aspnetcore #23218.UseRequestLocalizationphải xuất hiện trước bất kỳ middleware nào có thể kiểm tra request culture (văn hóa request), ví dụapp.UseStaticFiles().- UseRateLimiter phải được gọi sau
UseRoutingkhi sử dụng các API rate limiting endpoint-specific. Ví dụ, nếu thuộc tính[EnableRateLimiting]được sử dụng,UseRateLimiterphải được gọi sauUseRouting. Khi chỉ gọi global limiter (bộ giới hạn toàn cục),UseRateLimitercó thể được gọi trướcUseRouting.
Các thành phần middleware sau được cung cấp cùng với ASP.NET Core và nên được sắp xếp theo thứ tự điển hình:
- Xử lý exception/lỗi
- Khi ứng dụng chạy trong môi trường
Development: - Developer Exception Page Middleware (UseDeveloperExceptionPage) báo cáo lỗi runtime ứng dụng.
- Database Error Page Middleware (UseDatabaseErrorPage) báo cáo lỗi runtime cơ sở dữ liệu.
- Khi ứng dụng chạy trong môi trường
Production: - Exception Handler Middleware (UseExceptionHandler) bắt các exception được ném trong các middleware tiếp theo.
- HTTP Strict Transport Security Protocol (HSTS) Middleware (UseHsts) thêm header
Strict-Transport-Security. - HTTPS Redirection Middleware (UseHttpsRedirection) chuyển hướng các HTTP request đến HTTPS.
- Static File Middleware (UseStaticFiles) trả về các file tĩnh và ngắn mạch xử lý request tiếp theo.
- Cookie Policy Middleware (UseCookiePolicy) tuân thủ ứng dụng theo các quy định GDPR (Quy định Bảo vệ Dữ liệu Chung của EU).
- Routing Middleware (UseRouting) để route (định tuyến) các request.
- Authentication Middleware (UseAuthentication) cố gắng xác thực người dùng trước khi họ được phép truy cập các tài nguyên bảo mật.
- Authorization Middleware (UseAuthorization) ủy quyền cho người dùng truy cập các tài nguyên bảo mật.
- Session Middleware (UseSession) thiết lập và duy trì session state (trạng thái phiên). Nếu ứng dụng sử dụng session state, gọi Session Middleware sau Cookie Policy Middleware và trước MVC Middleware.
- Endpoint Routing Middleware (UseEndpoints với MapRazorPages) để thêm các Razor Pages endpoint vào request pipeline.
Thứ tự của UseCors và UseStaticFiles
Để biết thêm thông tin về thứ tự của UseCors và UseStaticFiles, xem Enable Cross-Origin Requests (CORS) in ASP.NET Core.
Thứ tự của Forwarded Headers Middleware
Chạy Forwarded Headers Middleware (Middleware chuyển tiếp header) trước các middleware khác để đảm bảo rằng middleware dựa vào thông tin forwarded headers (header được chuyển tiếp) có thể sử dụng các giá trị header để xử lý. Để chạy Forwarded Headers Middleware sau Diagnostics và Error Handling Middleware, xem Forwarded Headers Middleware order.
Middleware được tích hợp sẵn
Phiên bản mới nhất của ASP.NET Core đi kèm với các middleware sau:
| Middleware | Mô tả | Thứ tự |
|---|---|---|
| Antiforgery | Cung cấp hỗ trợ chống request giả mạo. | Sau authentication và authorization, trước endpoints. |
| Authentication | Cung cấp hỗ trợ authentication (xác thực). | Trước khi HttpContext.User là bắt buộc. Terminal cho OAuth callbacks. |
| Authorization | Cung cấp hỗ trợ authorization (ủy quyền). | Ngay sau Authentication Middleware. |
| Cookie Policy | Theo dõi sự đồng ý từ người dùng để lưu thông tin cá nhân và thực thi các tiêu chuẩn tối thiểu cho các trường cookie. | Trước middleware phát hành cookie. Ví dụ: Authentication, Session, MVC (TempData). |
| CORS | Cấu hình Cross-Origin Resource Sharing. | Trước middleware sử dụng CORS. |
| Developer Exception Page | Tạo một trang với thông tin lỗi chỉ dùng trong môi trường Development. | Trước middleware tạo ra lỗi. |
| Diagnostics | Một số middleware riêng biệt cung cấp trang developer exception, xử lý exception, trang status code và trang web mặc định cho các ứng dụng mới. | Trước middleware tạo ra lỗi. Terminal cho exceptions hoặc phục vụ trang web mặc định cho ứng dụng mới. |
| Forwarded Headers | Chuyển tiếp các header được proxy vào request hiện tại. | Trước middleware sử dụng các trường được cập nhật. |
| Health Check | Kiểm tra sức khỏe của ứng dụng ASP.NET Core và các dependency của nó. | Terminal nếu request khớp với endpoint health check. |
| HTTP Logging | Ghi log HTTP Requests và Responses. | Ở đầu middleware pipeline. |
| HTTPS Redirection | Chuyển hướng tất cả HTTP request đến HTTPS. | Trước middleware sử dụng URL. |
| HTTP Strict Transport Security (HSTS) | Middleware cải thiện bảo mật thêm một response header đặc biệt. | Trước khi response được gửi và sau middleware sửa đổi request. |
| MVC | Xử lý request với MVC/Razor Pages. | Terminal nếu request khớp với một route. |
| Output Caching | Cung cấp hỗ trợ cache (lưu bộ đệm) response dựa trên cấu hình. | Trước middleware yêu cầu caching. |
| Response Caching | Cung cấp hỗ trợ cache response. | Trước middleware yêu cầu caching. |
| Request Decompression | Cung cấp hỗ trợ giải nén request. | Trước middleware đọc request body. |
| Response Compression | Cung cấp hỗ trợ nén response. | Trước middleware yêu cầu nén. |
| Request Localization | Cung cấp hỗ trợ bản địa hóa. | Trước middleware nhạy cảm với localization. |
| Endpoint Routing | Định nghĩa và ràng buộc các request route. | Terminal cho các route khớp. |
| Session | Cung cấp hỗ trợ quản lý user session. | Trước middleware yêu cầu Session. |
| Static File | Cung cấp hỗ trợ phục vụ file tĩnh và duyệt thư mục. | Terminal nếu request khớp với một file. |
| URL Rewrite | Cung cấp hỗ trợ viết lại URL và chuyển hướng request. | Trước middleware sử dụng URL. |
| WebSockets | Kích hoạt giao thức WebSockets. | Trước middleware cần chấp nhận WebSocket request. |