Viết Custom ASP.NET Core Middleware (Phần mềm trung gian tùy chỉnh)
Bởi Fiyaz Hasan, Rick Anderson, và Steve Smith
Middleware (phần mềm trung gian) là phần mềm được lắp ráp vào app pipeline (đường ống ứng dụng) để xử lý request (yêu cầu) và response (phản hồi). ASP.NET Core cung cấp bộ built-in middleware components (thành phần middleware tích hợp) phong phú, nhưng trong một số tình huống bạn có thể muốn viết custom middleware (middleware tùy chỉnh).
Chủ đề này mô tả cách viết convention-based middleware (middleware dựa trên quy ước). Để biết phương pháp dùng strong typing (kiểu mạnh) và per-request activation (kích hoạt theo request), xem Factory-based middleware activation in ASP.NET Core.
Class Middleware
Middleware thường được đóng gói trong một class và exposed (hiển thị) với một extension method (phương thức mở rộng). Hãy xem xét inline middleware (middleware nội tuyến) sau đây, đặt culture (văn hóa) cho request hiện tại từ query string:
using System.Globalization;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseHttpsRedirection();
app.Use(async (context, next) =>
{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
// Gọi delegate/middleware tiếp theo trong pipeline
await next(context);
});
app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"CurrentCulture.DisplayName: {CultureInfo.CurrentCulture.DisplayName}");
});
app.Run();Inline middleware trên được dùng để minh họa việc tạo middleware component bằng cách gọi Microsoft.AspNetCore.Builder.UseExtensions.Use. Extension method Use trên thêm một middleware delegate (đại biểu) được định nghĩa in-line vào request pipeline của ứng dụng.
Có hai overload (quá tải) cho extension Use:
- Một nhận
HttpContextvàFunc<Task>. GọiFunc<Task>không có tham số nào. - Overload kia nhận
HttpContextvàRequestDelegate. GọiRequestDelegatebằng cách truyềnHttpContext.
Ưu tiên dùng overload sau vì nó tiết kiệm hai internal per-request allocations (phân bổ nội tại theo request) cần thiết khi dùng overload kia.
Kiểm thử middleware bằng cách truyền culture. Ví dụ, yêu cầu https://localhost:5001/?culture=es-es.
Đoạn code sau chuyển middleware delegate vào một class:
using System.Globalization;
namespace Middleware.Example;
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;
public RequestCultureMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
// Gọi delegate/middleware tiếp theo trong pipeline
await _next(context);
}
}Middleware class phải bao gồm:
- Một public constructor với tham số kiểu
RequestDelegate. - Một public method có tên
InvokehoặcInvokeAsync. Method này phải: - Trả về
Task. - Chấp nhận tham số đầu tiên kiểu
HttpContext.
Các tham số bổ sung cho constructor và Invoke/InvokeAsync được populate (điền) bởi dependency injection (DI - tiêm phụ thuộc).
Thông thường, một extension method được tạo để expose middleware qua IApplicationBuilder:
using System.Globalization;
namespace Middleware.Example;
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;
public RequestCultureMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
// Gọi delegate/middleware tiếp theo trong pipeline
await _next(context);
}
}
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}Đoạn code sau gọi middleware từ Program.cs:
using Middleware.Example;
using System.Globalization;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseHttpsRedirection();
app.UseRequestCulture();
app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"CurrentCulture.DisplayName: {CultureInfo.CurrentCulture.DisplayName}");
});
app.Run();Middleware dependencies (Phụ thuộc middleware)
Middleware nên tuân theo Explicit Dependencies Principle (Nguyên tắc phụ thuộc rõ ràng) bằng cách expose các dependency (phụ thuộc) của nó trong constructor. Middleware được construct (xây dựng) một lần cho mỗi application lifetime (vòng đời ứng dụng).
Middleware components có thể resolve (giải quyết) các dependency của chúng từ dependency injection (DI) qua constructor parameters. UseMiddleware cũng có thể chấp nhận các tham số bổ sung trực tiếp.
Per-request middleware dependencies (Phụ thuộc middleware theo request)
Middleware được construct khi ứng dụng khởi động, do đó có vòng đời ứng dụng. Các scoped lifetime services (dịch vụ vòng đời phạm vi) được dùng bởi middleware constructor không được chia sẻ với các kiểu được dependency-injected khác trong mỗi request. Để chia sẻ scoped service giữa middleware và các kiểu khác, hãy thêm các services này vào signature (chữ ký) của method InvokeAsync. Method InvokeAsync có thể chấp nhận các tham số bổ sung được populate bởi DI:
namespace Middleware.Example;
public class MyCustomMiddleware
{
private readonly RequestDelegate _next;
public MyCustomMiddleware(RequestDelegate next)
{
_next = next;
}
// IMessageWriter được inject vào InvokeAsync
public async Task InvokeAsync(HttpContext httpContext, IMessageWriter svc)
{
svc.Write(DateTime.Now.Ticks.ToString());
await _next(httpContext);
}
}
public static class MyCustomMiddlewareExtensions
{
public static IApplicationBuilder UseMyCustomMiddleware(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyCustomMiddleware>();
}
}Đoạn code sau được dùng để kiểm thử middleware trên:
using Middleware.Example;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IMessageWriter, LoggingMessageWriter>();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseMyCustomMiddleware();
app.MapGet("/", () => "Hello World!");
app.Run();Interface IMessageWriter và implementation (triển khai):
namespace Middleware.Example;
public interface IMessageWriter
{
void Write(string message);
}
public class LoggingMessageWriter : IMessageWriter
{
private readonly ILogger<LoggingMessageWriter> _logger;
public LoggingMessageWriter(ILogger<LoggingMessageWriter> logger) =>
_logger = logger;
public void Write(string message) =>
_logger.LogInformation(message);
}Tài nguyên bổ sung
- Lifetime and registration options chứa ví dụ đầy đủ về middleware với các scoped, transient, và singleton lifetime services.
- ASP.NET Core Middleware
- Test ASP.NET Core middleware
- Factory-based middleware activation in ASP.NET Core