写入自定义 ASP.NET Core 中间件Write custom ASP.NET Core middleware

作者:Rick AndersonSteve SmithBy Rick Anderson and Steve Smith

中间件是一种装配到应用管道以处理请求和响应的软件。Middleware is software that's assembled into an app pipeline to handle requests and responses. ASP.NET Core 提供了一组丰富的内置中间件组件,但在某些情况下,你可能需要写入自定义中间件。ASP.NET Core provides a rich set of built-in middleware components, but in some scenarios you might want to write a custom middleware.

备注

本主题介绍如何编写基于约定的中间件。This topic describes how to write convention-based middleware. 有关使用强类型和按请求激活的方法,请参阅 ASP.NET Core 中基于工厂的中间件激活For an approach that uses strong typing and per-request activation, see ASP.NET Core 中基于工厂的中间件激活.

中间件类Middleware class

通常,中间件封装在类中,并且通过扩展方法公开。Middleware is generally encapsulated in a class and exposed with an extension method. 请考虑以下中间件,该中间件通过查询字符串设置当前请求的区域性:Consider the following middleware, which sets the culture for the current request from a query string:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        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;
            }

            // Call the next delegate/middleware in the pipeline
            await next();
        });

        app.Run(async (context) =>
        {
            await context.Response.WriteAsync(
                $"Hello {CultureInfo.CurrentCulture.DisplayName}");
        });

    }
}

以上示例代码用于演示创建中间件组件。The preceding sample code is used to demonstrate creating a middleware component. 有关 ASP.NET Core 的内置本地化支持,请参阅 <xref:fundamentals/localization>。For ASP.NET Core's built-in localization support, see <xref:fundamentals/localization>.

通过传入区域性测试中间件。Test the middleware by passing in the culture. 例如,请求 https://localhost:5001/?culture=noFor example, request https://localhost:5001/?culture=no.

以下代码将中间件委托移动到类:The following code moves the middleware delegate to a class:

using Microsoft.AspNetCore.Http;
using System.Globalization;
using System.Threading.Tasks;

namespace Culture
{
    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;

            }

            // Call the next delegate/middleware in the pipeline
            await _next(context);
        }
    }
}

必须包括中间件类:The middleware class must include:

  • 具有类型为 RequestDelegate 的参数的公共构造函数。A public constructor with a parameter of type RequestDelegate.
  • 名为 InvokeInvokeAsync 的公共方法。A public method named Invoke or InvokeAsync. 此方法必须:This method must:
    • 返回 TaskReturn a Task.
    • 接受类型 HttpContext 的第一个参数。Accept a first parameter of type HttpContext.

构造函数和 Invoke/InvokeAsync 的其他参数由依赖关系注入 (DI) 填充。Additional parameters for the constructor and Invoke/InvokeAsync are populated by dependency injection (DI).

中间件依赖项Middleware dependencies

中间件应通过在其构造函数中公开其依赖项来遵循显式依赖项原则Middleware should follow the Explicit Dependencies Principle by exposing its dependencies in its constructor. 在每个应用程序生存期构造一次中间件。Middleware is constructed once per application lifetime. 如果需要与请求中的中间件共享服务,请参阅按请求中间件依赖项部分。See the Per-request middleware dependencies section if you need to share services with middleware within a request.

中间件组件可通过构造函数参数从依赖关系注入 (DI) 解析其依赖项。Middleware components can resolve their dependencies from dependency injection (DI) through constructor parameters. UseMiddleware<T> 也可直接接受其他参数。UseMiddleware<T> can also accept additional parameters directly.

按请求中间件依赖项Per-request middleware dependencies

由于中间件是在应用启动时构造的,而不是按请求构造的,因此在每个请求过程中,中间件构造函数使用的范围内生存期服务不与其他依赖关系注入类型共享。Because middleware is constructed at app startup, not per-request, scoped lifetime services used by middleware constructors aren't shared with other dependency-injected types during each request. 如果必须在中间件和其他类型之间共享范围内服务,请将这些服务添加到 Invoke 方法的签名。If you must share a scoped service between your middleware and other types, add these services to the Invoke method's signature. Invoke 方法可接受由 DI 填充的其他参数:The Invoke method can accept additional parameters that are populated by DI:

public class CustomMiddleware
{
    private readonly RequestDelegate _next;

    public CustomMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    // IMyScopedService is injected into Invoke
    public async Task Invoke(HttpContext httpContext, IMyScopedService svc)
    {
        svc.MyProperty = 1000;
        await _next(httpContext);
    }
}

中间件扩展方法Middleware extension method

以下扩展方法通过 IApplicationBuilder 公开中间件:The following extension method exposes the middleware through IApplicationBuilder:

using Microsoft.AspNetCore.Builder;

namespace Culture
{
    public static class RequestCultureMiddlewareExtensions
    {
        public static IApplicationBuilder UseRequestCulture(
            this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<RequestCultureMiddleware>();
        }
    }
}

以下代码通过 Startup.Configure 调用中间件:The following code calls the middleware from Startup.Configure:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.UseRequestCulture();

        app.Run(async (context) =>
        {
            await context.Response.WriteAsync(
                $"Hello {CultureInfo.CurrentCulture.DisplayName}");
        });
    }
}

其他资源Additional resources