Use​Middleware​Extensions

前言

本文编写时源码参考github仓库主分支。

aspnetcore提供了Use方法供开发者自定义中间件,该方法接收一个委托对象,该委托接收一个RequestDelegate对象,并返回一个RequestDelegate对象,方法定义如下:

IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);

委托RequestDelegate的定义

/// <summary>
/// A function that can process an HTTP request.
/// </summary>
/// <param name="context">The <see cref="HttpContext"/> for the request.</param>
/// <returns>A task that represents the completion of request processing.</returns>
public delegate Task RequestDelegate(HttpContext context);

如果我们直接使用IApplicationBuilder.Use来写中间件逻辑,可以使用lamda表达式来简化代码,如下:

app.Use((RequestDelegate next) =>
{
    return (HttpContext ctx) =>
    {
        // do your logic
        return next(ctx);
    };
});

如果写一些简单的逻辑,这种方式最为方便,问题是如果需要写的中间件代码比较多,依然这样去写,会导致我们Program.cs文件代码非常多,如果有多个中间件,那么最后我们的的Program.cs文件包含多个中间件代码,看上去十分混乱。

将中间件逻辑独立出来

为了解决我们上面的代码不优雅,我们希望能将每个中间件业务独立成一个文件,多个中间件代码不混乱的搞到一起。我们需要这样做。

单独的中间件文件

// Middleware1.cs
public class Middleware1
{
    public static RequestDelegate Logic(RequestDelegate requestDelegate)
    {
        return (HttpContext ctx) =>
        {
            // do your logic
            return requestDelegate(ctx);
        };
    }
}

调用中间件

app.Use(Middleware1.Logic);
// 以下是其他中间件示例
app.Use(Middleware2.Logic);
app.Use(Middleware3.Logic);
app.Use(Middleware4.Logic);

这种方式可以很好的将各个中间件逻辑独立出来,Program.cs此时变得十分简洁,然而我们还不满足这样,因为我们的Logic方法中直接返回一个lamada表达式(RequestDelegate对象),代码层级深了一层,每个中间件都多写这一层壳似乎不太优雅,能不能去掉这层lamada表达式呢?

UseMiddlewareExtensions

为了解决上面提到的痛点,UseMiddlewareExtensions扩展类应运而生,它在Aspnetcore底层大量使用,它主要提供一个泛型UseMiddleware<T>方法用来方便我们注册中间件,下面是该方法的定义

public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder app, params object?[] args)

如果只看这个方法的声明,估计没人知道如何使用,因为该方法接收的泛型参数TMiddleware没有添加任何限制,而另一个args参数也是object类型,而且是可以不传的,也就是它只需要传任意一个类型都不会在编译时报错。

比如这样,完全不会报错:



当然,如果你这样就运行程序,一定会收到下面的异常

System.InvalidOperationException:“No public 'Invoke' or 'InvokeAsync' method found for middleware of type 'System.String'.”

提示我们传的类型没有InvokeInvokeAsync公共方法,这里大概能猜到,底层应该是通过反射进行动态调用InvokeInvokeAsync公共方法的。

源码分析

想要知道其本质,唯有查看源码,以下源码来自UseMiddlewareExtensions

如下,该扩展类一共提供两个并且是重载的公共方法UseMiddleware,一般都只会使用第一个UseMiddleware,第一个UseMiddleware方法内部再去调用第二个UseMiddleware方法,源码中对类型前面添加的[DynamicallyAccessedMembers(MiddlewareAccessibility)]属性可以忽略,它的作用是为了告诉编译器我们通过反射访问的范围,以防止对程序集对我们可能调用的方法或属性等进行裁剪。


internal const string InvokeMethodName = "Invoke";
internal const string InvokeAsyncMethodName = "InvokeAsync"; /// <summary>
/// Adds a middleware type to the application's request pipeline.
/// </summary>
/// <typeparam name="TMiddleware">The middleware type.</typeparam>
/// <param name="app">The <see cref="IApplicationBuilder"/> instance.</param>
/// <param name="args">The arguments to pass to the middleware type instance's constructor.</param>
/// <returns>The <see cref="IApplicationBuilder"/> instance.</returns>
public static IApplicationBuilder UseMiddleware<[DynamicallyAccessedMembers(MiddlewareAccessibility)] TMiddleware>(this IApplicationBuilder app, params object?[] args)
{
    return app.UseMiddleware(typeof(TMiddleware), args);
} /// <summary>
/// Adds a middleware type to the application's request pipeline.
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/> instance.</param>
/// <param name="middleware">The middleware type.</param>
/// <param name="args">The arguments to pass to the middleware type instance's constructor.</param>
/// <returns>The <see cref="IApplicationBuilder"/> instance.</returns>
public static IApplicationBuilder UseMiddleware(
    this IApplicationBuilder app,
    [DynamicallyAccessedMembers(MiddlewareAccessibility)] Type middleware,
    params object?[] args)
{
    if (typeof(IMiddleware).IsAssignableFrom(middleware))
    {
        // IMiddleware doesn't support passing args directly since it's
        // activated from the container
        if (args.Length > 0)
        {
            throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));
        }         return UseMiddlewareInterface(app, middleware);
    }     var applicationServices = app.ApplicationServices;
    var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
    MethodInfo? invokeMethod = null;
    foreach (var method in methods)
    {
        if (string.Equals(method.Name, InvokeMethodName, StringComparison.Ordinal) || string.Equals(method.Name, InvokeAsyncMethodName, StringComparison.Ordinal))
        {
            if (invokeMethod is not null)
            {
                throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));
            }             invokeMethod = method;
        }
    }     if (invokeMethod is null)
    {
        throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));
    }     if (!typeof(Task).IsAssignableFrom(invokeMethod.ReturnType))
    {
        throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));
    }     var parameters = invokeMethod.GetParameters();
    if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
    {
        throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));
    }     var state = new InvokeMiddlewareState(middleware);     return app.Use(next =>
    {
        var middleware = state.Middleware;         var ctorArgs = new object[args.Length + 1];
        ctorArgs[0] = next;
        Array.Copy(args, 0, ctorArgs, 1, args.Length);
        var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
        if (parameters.Length == 1)
        {
            return (RequestDelegate)invokeMethod.CreateDelegate(typeof(RequestDelegate), instance);
        }         var factory = Compile<object>(invokeMethod, parameters);         return context =>
        {
            var serviceProvider = context.RequestServices ?? applicationServices;
            if (serviceProvider == null)
            {
                throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
            }             return factory(instance, context, serviceProvider);
        };
    });
}

第一个UseMiddleware可以直接跳过,看第二个UseMiddleware方法,该方法一上来就先判断我们传的泛型类型是不是IMiddleware接口的派生类,如果是,直接交给UseMiddlewareInterface方法。

if (typeof(IMiddleware).IsAssignableFrom(middleware))
 {
     // IMiddleware doesn't support passing args directly since it's
     // activated from the container
     if (args.Length > 0)
     {
         throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));
     }      return UseMiddlewareInterface(app, middleware);
 }

这里总算看到应该有的东西了,如果声明UseMiddleware<T>方法时,对泛型T添加IMiddleware限制,我们不看源码就知道如何编写我们的中间件逻辑了,只需要写一个类,继承IMiddleware并实现InvokeAsync方法即可, UseMiddlewareInterface方法的实现比较简单,因为我们继承了接口,逻辑相对会简单点。

private static IApplicationBuilder UseMiddlewareInterface(
    IApplicationBuilder app,
    Type middlewareType)
{
    return app.Use(next =>
    {
        return async context =>
        {
            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);
            }
        };
    });
}
public interface IMiddleware
{
    /// <summary>
    /// Request handling method.
    /// </summary>
    /// <param name="context">The <see cref="HttpContext"/> for the current request.</param>
    /// <param name="next">The delegate representing the remaining middleware in the request pipeline.</param>
    /// <returns>A <see cref="Task"/> that represents the execution of this middleware.</returns>
    Task InvokeAsync(HttpContext context, RequestDelegate next);
}

如果我们的类不满足IMiddleware,继续往下看

通过反射查找泛型类中InvokeInvokeAsync方法

var applicationServices = app.ApplicationServices;
var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
MethodInfo? invokeMethod = null;
foreach (var method in methods)
{
    if (string.Equals(method.Name, InvokeMethodName, StringComparison.Ordinal) || string.Equals(method.Name, InvokeAsyncMethodName, StringComparison.Ordinal))
    {
// 如果Invoke和InvokeAsync同时存在,则抛出异常,也就是,我们只能二选一
        if (invokeMethod is not null)
        {
            throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));
        }         invokeMethod = method;
    }
} // 如果找不到Invoke和InvokeAsync则抛出异常,上文提到的那个异常。
if (invokeMethod is null)
{
    throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));
} // 如果Invoke和InvokeAsync方法的返回值不是Task或Task的派生类,则抛出异常
if (!typeof(Task).IsAssignableFrom(invokeMethod.ReturnType))
{
    throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));
}
Snippet // 如果Invoke和InvokeAsync方法没有参数,或第一个参数不是HttpContext,抛异常
var parameters = invokeMethod.GetParameters();
if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
{
    throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));
}

上面一堆逻辑主要就是检查我们的InvokeInvokeAsync方法是否符合要求,即:必须是接收HttpContext参数,返回Task对象,这恰好就是委托RequestDelegate的定义。

构造RequestDelegate

这部分源码的解读都注释到相应的位置了,如下

var state = new InvokeMiddlewareState(middleware);
// 调用Use函数,向管道中注册中间件
return app.Use(next =>
{
var middleware = state.Middleware; var ctorArgs = new object[args.Length + 1];
// next是RequestDelegate对象,作为构造函数的第一个参数传入
ctorArgs[0] = next;
Array.Copy(args, 0, ctorArgs, 1, args.Length);
// 反射实例化我们传入的泛型类,并把next和args作为构造函数的参数传入
var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
// 如果我们的Invoke方法只有一个参数,则直接创建该方法的委托
if (parameters.Length == 1)
{
return (RequestDelegate)invokeMethod.CreateDelegate(typeof(RequestDelegate), instance);
} // 当Invoke方法不止一个参数HttpContext,通过Compile函数创建动态表达式目录树,
// 表达式目录树的构造此处略过,其目的是实现将除第一个参数的其他参数通过IOC注入
var factory = Compile<object>(invokeMethod, parameters); return context =>
{
// 获取serviceProvider用于在上面构造的表达式目录树中实现依赖注入
var serviceProvider = context.RequestServices ?? applicationServices;
if (serviceProvider == null)
{
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
}
// 将所需的参数传入构造的表达式目录树工厂
return factory(instance, context, serviceProvider);
};
});

至此,整个扩展类的源码就解读完了。

通过UseMiddleware注入自定义中间件

通过上面的源码解读,我们知道了其实我们传入的泛型类型是有严格的要求的,主要有两种

通过继承IMiddleware

继承IMiddleware并实现该接口的InvokeAsync函数

public class Middleware1 : IMiddleware
{
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        // do your logic
        await next(context);
    }
}

通过反射

我们知道,在不继承IMiddleware的情况下,底层会通过反射实例化泛型类型,并通过构造函数传入RequestDelegate,而且要有一个公共函数InvokeInvokeAsync,并且接收的第一个参数是HttpContext,返回Task,根据要求我们将Middleware1.cs改造如下

public class Middleware1
{
    RequestDelegate next;     public Middleware1(RequestDelegate next)
    {
        this.next = next;
    } public async Task Invoke(HttpContext httpContext)
{
// do your logic
await this.next(httpContext);
}
}

总结

通过源码的学习,我们弄清楚底层注册中间件的来龙去脉,两种方式根据自己习惯进行使用,笔者认为通过接口的方式更加简洁直观简单,并且省去了反射带来的性能损失,推荐使用。既然通过继承接口那么爽,为啥还费那么大劲实现反射的方式呢?由源码可知,如果继承接口的话,就不能进行动态传参了。

if (typeof(IMiddleware).IsAssignableFrom(middleware))
        {
            // IMiddleware doesn't support passing args directly since it's
            // activated from the container
            if (args.Length > 0)
            {
                throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));
            }             return UseMiddlewareInterface(app, middleware);
        }

所以在需要传参的场景,则必须使用反射的方式,所以两种方式都有其存在的必要。

如果本文对您有帮助,还请点赞转发关注一波支持作者。

AspNetCore7.0源码解读之UseMiddleware的更多相关文章

  1. AFNetworking 3.0 源码解读 总结(干货)(下)

    承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...

  2. AFNetworking 3.0 源码解读(十一)之 UIButton/UIProgressView/UIWebView + AFNetworking

    AFNetworking的源码解读马上就结束了,这一篇应该算是倒数第二篇,下一篇会是对AFNetworking中的技术点进行总结. 前言 上一篇我们总结了 UIActivityIndicatorVie ...

  3. AFNetworking 3.0 源码解读(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking

    我们应该看到过很多类似这样的例子:某个控件拥有加载网络图片的能力.但这究竟是怎么做到的呢?看完这篇文章就明白了. 前言 这篇我们会介绍 AFNetworking 中的3个UIKit中的分类.UIAct ...

  4. AFNetworking 3.0 源码解读(九)之 AFNetworkActivityIndicatorManager

    让我们的APP像艺术品一样优雅,开发工程师更像是一名匠人,不仅需要精湛的技艺,而且要有一颗匠心. 前言 AFNetworkActivityIndicatorManager 是对状态栏中网络激活那个小控 ...

  5. AFNetworking 3.0 源码解读(八)之 AFImageDownloader

    AFImageDownloader 这个类对写DownloadManager有很大的借鉴意义.在平时的开发中,当我们使用UIImageView加载一个网络上的图片时,其原理就是把图片下载下来,然后再赋 ...

  6. AFNetworking 3.0 源码解读(七)之 AFAutoPurgingImageCache

    这篇我们就要介绍AFAutoPurgingImageCache这个类了.这个类给了我们临时管理图片内存的能力. 前言 假如说我们要写一个通用的网络框架,除了必备的请求数据的方法外,必须提供一个下载器来 ...

  7. AFNetworking 3.0 源码解读(六)之 AFHTTPSessionManager

    AFHTTPSessionManager相对来说比较好理解,代码也比较短.但却是我们平时可能使用最多的类. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilit ...

  8. AFNetworking 3.0 源码解读(三)之 AFURLRequestSerialization

    这篇就讲到了跟请求相关的类了 关于AFNetworking 3.0 源码解读 的文章篇幅都会很长,因为不仅仅要把代码进行详细的的解释,还会大概讲解和代码相关的知识点. 上半篇: URI编码的知识 关于 ...

  9. AFNetworking 3.0 源码解读(四)之 AFURLResponseSerialization

    本篇是AFNetworking 3.0 源码解读的第四篇了. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager AFNetworking 3 ...

随机推荐

  1. kafka producer 源码总结

    kafka producer可以总体上分为两个部分: producer调用send方法,将消息存放到内存中 sender线程轮询的从内存中将消息通过NIO发送到网络中 1 调用send方法 其实在调用 ...

  2. 怎么创建maven项目

    1.Eclipse中用Maven创建项目 2.点击[next] 3.选maven-archetype-webapp后,next 4.填写相应的信息,Packaged是默认创建一个包,不写也可以 4右击 ...

  3. matlab拟合函数的三种方法

    方法一:多项式拟合polyfit 1 x=[1 2 3 4 5 6 7 8 9]; 2 3 y=[9 7 6 3 -1 2 5 7 20]; 4 P= polyfit(x, y, 3) %三阶多项式拟 ...

  4. 探讨:微信小程序应该如何设计

    微信小程序公测后,开发者非常热情,都有很高的期待,都想抓住这一波红利.但是热情背后需要冷静,我们需要搞清楚两个问题: 微信想要我们做什么?微信小程序可以做什么? 微信想要我们做什么? 首先来弄清楚微信 ...

  5. 介绍一项让 React 可以与 Vue 抗衡的技术

    好吧,我承认我是标题党.React 明明如日中天,把它与 Vue 倒过来,给 Vue 加点东西或可与 React 抗衡.不过,这两年 Vue 干的正是这事,不断加东西,不断优化,按它现有发展速度超越 ...

  6. android JS 互相通讯

    1.android中利用webview调用网页上的js代码. Android 中可以通过webview来实现和js的交互,在程序中调用js代码,只需要将webview控件的支持js的属性设置为true ...

  7. React+dva+webpack+antd-mobile 实战分享(一)

    再看本篇文章之前,本人还是建议想入坑react的童鞋可以选有create-react-app来创建react的项目,因为现在dva和roadhog还不成熟,坑相对要多一些,当然如果你已经做好跳坑的准备 ...

  8. python大佬养成计划----基于flask_sqlalchemy的网页显示数据库信息

    网页显示数据库信息 使用我们刚学习的flask_sqlalchemy,在网页中显示数据库表中的数据.在开始运行程序前,确保数据库中执行过创建表和创建用户的操作,详见链接描述. # 模板文件templa ...

  9. HTML5 Canvas学习之路(六)

    一个炫酷的计时器 在慕课网看到一个canvas的课,感觉很炫酷,就把它看完了,然后记下来.http://www.imooc.com/learn/133 第一步:绘制要显示的时间 拿小球来绘制具体的数字 ...

  10. 【每日日报】第三十二天---DataOutputStream写文件

    1 今天继续看书 DataOutputStream写文件 1 package File; 2 import java.io.IOException; 3 import java.io.FileOutp ...