Nguon: Microsoft Learn · .NET 8.0

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:

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:

csharp
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:

csharp
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ể:

Để 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:

csharp
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 đó.

RequestResponse
/Hello from the non-Map delegate.
/map1Map 1
/map2Map 2
/map3Hello 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":

csharp
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:

csharp
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ị:

csharp
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:

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:

  1. Xử lý exception/lỗi
  2. Khi ứng dụng chạy trong môi trường Development:
  3. Developer Exception Page Middleware (UseDeveloperExceptionPage) báo cáo lỗi runtime ứng dụng.
  4. Database Error Page Middleware (UseDatabaseErrorPage) báo cáo lỗi runtime cơ sở dữ liệu.
  5. Khi ứng dụng chạy trong môi trường Production:
  6. Exception Handler Middleware (UseExceptionHandler) bắt các exception được ném trong các middleware tiếp theo.
  7. HTTP Strict Transport Security Protocol (HSTS) Middleware (UseHsts) thêm header Strict-Transport-Security.
  8. HTTPS Redirection Middleware (UseHttpsRedirection) chuyển hướng các HTTP request đến HTTPS.
  9. Static File Middleware (UseStaticFiles) trả về các file tĩnh và ngắn mạch xử lý request tiếp theo.
  10. 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).
  11. Routing Middleware (UseRouting) để route (định tuyến) các request.
  12. 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.
  13. Authorization Middleware (UseAuthorization) ủy quyền cho người dùng truy cập các tài nguyên bảo mật.
  14. 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.
  15. Endpoint Routing Middleware (UseEndpoints với MapRazorPages) để thêm các Razor Pages endpoint vào request pipeline.

Thứ tự của UseCorsUseStaticFiles

Để biết thêm thông tin về thứ tự của UseCorsUseStaticFiles, 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:

MiddlewareMô tảThứ tự
AntiforgeryCung cấp hỗ trợ chống request giả mạo.Sau authentication và authorization, trước endpoints.
AuthenticationCung cấp hỗ trợ authentication (xác thực).Trước khi HttpContext.User là bắt buộc. Terminal cho OAuth callbacks.
AuthorizationCung cấp hỗ trợ authorization (ủy quyền).Ngay sau Authentication Middleware.
Cookie PolicyTheo 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).
CORSCấu hình Cross-Origin Resource Sharing.Trước middleware sử dụng CORS.
Developer Exception PageTạ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.
DiagnosticsMộ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 HeadersChuyể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 CheckKiể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 LoggingGhi log HTTP Requests và Responses.Ở đầu middleware pipeline.
HTTPS RedirectionChuyể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.
MVCXử lý request với MVC/Razor Pages.Terminal nếu request khớp với một route.
Output CachingCung 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 CachingCung cấp hỗ trợ cache response.Trước middleware yêu cầu caching.
Request DecompressionCung cấp hỗ trợ giải nén request.Trước middleware đọc request body.
Response CompressionCung cấp hỗ trợ nén response.Trước middleware yêu cầu nén.
Request LocalizationCung 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.
SessionCung cấp hỗ trợ quản lý user session.Trước middleware yêu cầu Session.
Static FileCung 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 RewriteCung cấp hỗ trợ viết lại URL và chuyển hướng request.Trước middleware sử dụng URL.
WebSocketsKích hoạt giao thức WebSockets.Trước middleware cần chấp nhận WebSocket request.