ASP.NET Core Middleware

中间件(Middleware)是ASP.NET Core中的一个重要特性。**所谓中间件就是嵌入到应用管道中用于处理请求和响应的一段代码**。ASP.NET Core Middleware可以分为两种类型:

Conventional Middleware

这种中间件没有实现特定的接口或者继承特定类,它更像是Duck Typing (你走起路来像个鸭子, 叫起来像个鸭子, 那么你就是个鸭子)。有两种表现形式:

匿名方法

这种方式又称为内联中间件(in-line middleware),可以使用RunMap, Use ,MapWhen等扩展方法来实现。如:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {

            // Do work that doesn't write to the Response.
            await next.Invoke();
            // Do logging or other work that doesn't write to the Response.
        });
    }
}

IApplicationBuilder的扩展方法:RunMapMapWhen

Use(this IApplicationBuilder app, Func<HttpContext, Func<Task>, Task> middleware),最终都会调用IApplicationBuilder接口中的Use(Func<RequestDelegate, RequestDelegate> middleware)方法来实现向请求处理管道中注入中间件,后面会对源码做分析。

自定义中间件类

这种形式利于代码的复用,如:

public class XfhMiddleware
{
    private readonly RequestDelegate \_next;

    //在应用程序的生命周期中,中间件的构造函数只会被调用一次
    public XfhMiddleware(RequestDelegate next)
    {
        this.\_next = next;
    }


    public async Task InvokeAsync(HttpContext context)
    {
        // Do something...
        await \_next(context);
    }

}


public static class XfhMiddlewareExtension
{
    public static IApplicationBuilder UseXfhMiddleware(this IApplicationBuilder builder)
    {
        // 使用UseMiddleware将自定义中间件添加到请求处理管道中
        return builder.UseMiddleware<XfhMiddleware>();
    }
}

将自定义中间件配置到请求处理管道中

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseXfhMiddleware();
}

IMiddleware

IMiddleware提供了强类型约束的中间件,其默认实现是MiddlewareFactory,接口定义如下:

public interface IMiddleware
{
    Task InvokeAsync(HttpContext context, RequestDelegate next);
}

IMiddlewareFactory用于创建IMiddleware实例及对实例进行回收,接口定义:

public interface IMiddlewareFactory
{
    public IMiddleware Create (Type middlewareType);
    
    public void Release (IMiddleware middleware);
}

自定义IMiddleware类型中间件

public class MyMiddleware : IMiddleware
{
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        await next(context);
    }
}





public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyMiddleware>();
    }
}

将中间件注入到请求处理管道:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseMyMiddleware();
}

使用IMiddleware类型的中间件需要在容器中进行注册,否则抛异常,具体原因下面分析:

服务未注册

将中间件注入到容器中:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<MyMiddleware>();

    services.AddMvc();
}

一段警告

下面贴一段微软文档中的警告,大意是不要试图去改变已发往客户端的响应内容,否则可能会引发异常。实在是太懒了,不想翻译就把原文贴出来了:

WarningDon't call next.Invoke after the response has been sent to the client. Changes to HttpResponse after the response has started throw an exception. For example, changes such as setting headers and a status code throw an exception. Writing to the response body after calling next:May cause a protocol violation. For example, writing more than the stated Content-Length.May corrupt the body format. For example, writing an HTML footer to a CSS file.HasStarted is a useful hint to indicate if headers have been sent or the body has been written to.


UseMiddleware

前面将自定义中间件注入到请求处理管道时用到了UseMiddleware方法,从方法签名中可以看到UserMiddleware可以接受多个参数

public static class UseMiddlewareExtensions
{
    public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder app,  params object[] args);
   
    public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args);
}

接下来我们看下UserMiddleware方法的具体实现,由于该方法代码量较大,所以这里只看其中的关键部分,方法整体流程如下:

public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args)
{
    // IMiddleware类型
    if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo()))
    {
        // IMiddleware doesn't support passing args directly since it's
        // activated from the containe
        if (args.Length > 0)
        {
            throw new NotSupportedException(
                Resources.FormatException\_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));
        }

        return UseMiddlewareInterface(app, middleware);
    }
    

    // Conventional Middleware
    var applicationServices = app.ApplicationServices;
    return app.Use(next =>
    {
        // 判断传入的中间件是否符合约束
    });
}
  • 该方法首先判断传入的middleware是否是IMiddleware类型,如果是则调用UseMiddlewareInterface

从这段代码中可以看到IMiddlewareFactory负责创建并回收IMiddleware对象

public static class UseMiddlewareExtensions
{
    private static IApplicationBuilder UseMiddlewareInterface(IApplicationBuilder app, Type middlewareType)
    {
        return app.Use(next =>
        {
            return async context =>
            {
                // 从容器中获取IMiddlewareFactory实例
                var middlewareFactory =
                    (IMiddlewareFactory) context.RequestServices.GetService(typeof(IMiddlewareFactory));
                if (middlewareFactory == null)
                {
                    // No middleware factory
                    throw new InvalidOperationException(
                 Resources.FormatException\_UseMiddlewareNoMiddlewareFactory(typeof(IMiddlewareFactory)));
                }
                
                var middleware = middlewareFactory.Create(middlewareType);
                if (middleware == null)
                {
                    // The factory returned null, it's a broken implementation
                    throw new InvalidOperationException(
                        Resources.FormatException\_UseMiddlewareUnableToCreateMiddleware(middlewareFactory.GetType(),middlewareType));
                }
                

                try
                {
                    await middleware.InvokeAsync(context, next);
                }
                finally
                {
                    middlewareFactory.Release(middleware);
                }
            };
        });
    }
}

MiddlewareFactoryCreate方法中可以看到,IMiddleware实例是从容器中获取的,若容器中找不到则会抛出异常:

public class MiddlewareFactory : IMiddlewareFactory
{
    private readonly IServiceProvider \_serviceProvider;

    public MiddlewareFactory(IServiceProvider serviceProvider)
    {
        this.\_serviceProvider = serviceProvider;
    }

    public IMiddleware Create(Type middlewareType)
    {
        return ServiceProviderServiceExtensions.GetRequiredService(this.\_serviceProvider, middlewareType) as IMiddleware;
    }

    public void Release(IMiddleware middleware)
    {

    }
}
  • 若是Conventional Middleware则判断传入的middleware是否符合约束

首先判断传入的middleware中是否**仅包含一个名称为Invoke或InvokeAsync的公共实例方法**

// UseMiddlewareExtensions类中的两个常量

internal const string InvokeMethodName = "Invoke";

internal const string InvokeAsyncMethodName = "InvokeAsync";



// UserMiddleware方法

var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);

var invokeMethods = methods.Where(m =>

    string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)

    || string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)

).ToArray();

if (invokeMethods.Length > 1)
{
    throw new InvalidOperationException(

        Resources.FormatException\_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));
}

if (invokeMethods.Length == 0)
{
    throw new InvalidOperationException(
        Resources.FormatException\_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName,middleware));
}

其次判断方法的返回类型是否是Task

var methodInfo = invokeMethods[0];

if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType))
{
    throw new InvalidOperationException(
        Resources.FormatException\_UseMiddlewareNonTaskReturnType(InvokeMethodName,
            InvokeAsyncMethodName, nameof(Task)));
}

然后再判断,方法的第一个参数是否是HttpContext类型:

var parameters = methodInfo.GetParameters();

if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
{
    throw new InvalidOperationException(
        Resources.FormatException\_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName,
            nameof(HttpContext)));
}

对于InvokeInvokeAsync仅包含一个HttpContext类型参数的情况用到了反射(ActivatorUtilities.CreateInstance方法中)来构建RequestDelegate

var ctorArgs = new object[args.Length + 1];
ctorArgs[0] = next;
Array.Copy(args, 0, ctorArgs, 1, args.Length);
var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middlewa
if (parameters.Length == 1)
{
    return (RequestDelegate) methodInfo.CreateDelegate(typeof(RequestDelegate), in
}

对于包含多个参数的情况,则使用了表达式树来构建RequestDelegate

var factory = Compile<object>(methodInfo, parameters);
return context =>
{
    var serviceProvider = context.RequestServices ?? applicationServices;
    if (serviceProvider == null)
    {
        throw new InvalidOperationException(
            Resources.FormatException\_UseMiddlewareIServiceProviderNotAvailable(
                nameof(IServiceProvider)));
    }

    return factory(instance, context, serviceProvider);
};

完整的代码可以在Github上看到。

Use(Func<RequestDelegate, RequestDelegate> middleware)

上述所有中间件,最终都会调用IApplicationBuilder接口中的Use(Func<RequestDelegate, RequestDelegate> middleware)方法来实现向请求处理管道中注册中间件,该方法在ApplicationBuilder类的实现如下:

public class ApplicationBuilder : IApplicationBuilde
{
    private readonly IList<Func<RequestDelegate, RequestDelegate>> \_components =
        new List<Func<RequestDelegate, RequestDelegate>>();

    public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
    {
        this.\_components.Add(middleware);
        return this;
    }
}

从上面代码中可以看到,中间件是一个RequestDelegate类型的委托,请求处理管道其实是一个委托列表,请求委托签名如下:

public delegate Task RequestDelegate(HttpContext context);

与ASP.NET处理管道的区别

图片来自微软官方文档

传统的ASP.NET的处理管道是基于事件模型的,处理管道有多个IHttpModule和一个IHttpHandler组成。请求处理管道中各个模块被调用的顺序取决于两方面:

  • 模块所注册事件被触发的先后顺序
  • 注册同一事件的不同模块执行先后顺序有Web.config中的配置顺序决定
图片来自微软官方文档

ASP.NET Core的请求处理管道则是有一堆中间件组成,相对ASP.NET更简单。

中间件处理请求和响应的顺序只与其在代码中的注册顺序有关:处理请求按注册顺序依次执行,处理响应按注册顺序反方向依次执行。

其次,在ASP.NET Core中只需使用代码,而无需使用Global.asaxWeb.config来配置请求处理管道。

小结

所谓中间件就是嵌入到应用管道中用于处理请求和响应的一段代码,它主要有两个作用:

  • 处理请求和响应
  • 可以阻止请求发往请求处理管道中的下一个中间件

在ASP.NET Core中,中间件是以RequestDelegate委托的形式体现的。

ASP.NET Core中整个请求处理管道的创建是围绕这种IApplicationBuilder接口进行的,请求处理管道是一个List<RequestDelegate>类型的列表。

推荐阅读

ASP.NET Core Middleware

Factory-based middleware activation in ASP.NET Core

Migrate HTTP handlers and modules to ASP.NET Core middleware

ASP.NET MVC5请求处理管道和生命周期

用ASP.NET Core 2.0 建立规范的 REST API -- 预备知识

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏熊二哥

快速入门系列--CLR--01基本概念

在.NET平台用C#这么久,自然会发现其版本很多,相应的概念也会很多,常常都是萌萌哒。而在实际工作中经常会遇到需要配置dll版本号,公钥token等场景,因而对...

20260
来自专栏大内老A

ASP.NET Core的路由[3]:Router的创建者——RouteBuilder

在《注册URL模式与HttpHandler的映射关系》演示的实例中,我们总是利用一个RouteBuilder对象来为RouterMiddleware中间件创建所...

44440
来自专栏Danny的专栏

在类库中使用MessageBox

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huyuyang6688/article/...

17620
来自专栏恰童鞋骚年

ASP.Net MVC开发基础学习笔记:三、Razor视图引擎、控制器与路由机制学习

  在MVC3.0版本的时候,微软终于引入了第二种模板引擎:Razor。在这之前,我们一直在使用WebForm时代沿留下来的ASPX引擎或者第三方的NVeloc...

14530
来自专栏每日一篇技术文章

Foundation-RunLoop

10520
来自专栏me的随笔

ASP.NET Core Middleware

中间件(Middleware)是ASP.NET Core中的一个重要特性。所谓中间件就是嵌入到应用管道中用于处理请求和响应的一段代码。ASP.NET Core ...

16240
来自专栏hbbliyong

LINQ 图解 LINQ学习第三篇

LINQ,语言集成查询(Language INtegrated Query)是一组用于c#和Visual Basic语言的扩展。它允许编写C#或者Visual ...

31360
来自专栏偏前端工程师的驿站

.NET魔法堂:工程构建基石->MSBuild

一、前言                               MSBuild是一个既熟悉又陌生的名字,Visual Studio的项目加载和构建均通过M...

51180
来自专栏葡萄城控件技术团队

如何遍历当前进程中的AppDomain

.Net Framework居然没有提供托管的接口来获取当前进程中的其它AppDomain!所以,我们只有借助承载接口(Hosting Interfaces)来...

24080
来自专栏猿学

猿学-讲一下Asp.net core MVC2.1 里面的 ApiControllerAttribute

ASP.NET Core MVC 2.1 特意为构建 HTTP API 提供了一些小特性,今天主角就是 ApiControllerAttribute. (注:文...

8600

扫码关注云+社区

领取腾讯云代金券