前言

  由于是第一次写博客,如果您看到此文章,希望大家抱着找错误、批判的心态来看。 sky!

何为中间件?

在 ASP.NET Framework 中应该都知道请求管道。可参考:浅谈 ASP.NET 的内部机制 系列,个人感觉超详细。

题外话:

说到请求管道,就想以前还是超菜鸟时有次面试被问到这个问题,一脸懵逼只说了 Controller→Action→View。脸红啊!!

ASP.NET Core 中的中间件就是.net framework 请求管道的实现。下图演示了 Middlerware 的概念。 沿黑色箭头执行。

每一个中间件(Middleware1、Middleware2...)都是一个委托,这一系列委托就组成了整个管道。

中间件的写法

  1. 直接在Startup.cs类的Configure方法里写

    app.Use(async (context, next) =>
    {
    logger.LogInformation("中间件开始...");
    await next.Invoke(); //执行下一个中间件
    logger.LogInformation("中间件完成...");
    });

    结合上图:

    //logic对应logger.LogInformation("中间件开始...");

    next();对应await next.Invoke();

    //more logic对应logger.LogInformation("中间件完成...");

    其中//logic(即请求)是顺序执行。即:Middleware1→Middleware2→...→Middlewaren

    而//more logic(即响应)是倒序执行。即:Middlewaren→...→Middleware2→Middleware1

  2. 同 1,只是不用 Use 而是用 Run:

     app.Run(async context =>
    {
    await context.Response.WriteAsync("请求终止了,下一步将会执行已执行过的Middleware的 //more logic");
    });

    Run 会终止请求,即管道中最后一个中间件,后面详细剖析!

  3. 下面这种写法应该是比较合理的,也是比较优雅的

    新建一个类如下(该类是有强制规范的,详细见下文):

    public class RequestTestMiddleware
    {
    private readonly RequestDelegate _next;
    public RequestTestMiddleware(RequestDelegate next)
    {
    _next = next;
    }
    public async Task InvokeAsync(HttpContext context)
    {
    //中间件开始 logic
    await _next(context);//执行下一个中间件
    //中间件完成 more logic
    }
    }

    Startup.cs类的Configure方法里添加如下代码,效果和 1 相同:

    app.UseMiddleware<RequestTestMiddleware>();
    //app.UseMiddleware<RequestTestMiddleware>(params object[] parameters);//参数说明见下面

    不知发现了没,上面的InvokeAsync方法不是用的打印日志,而是用的注释。

    因为我们没有引用logger对象,了解过 ASP.NET Core 的肯定知道依赖注入,我们只需要把ILogger注入进来就行了,改造如下:

    public class RequestTestMiddleware
    {
    private readonly RequestDelegate _next;
    public RequestTestMiddleware(RequestDelegate next)
    {
    _next = next;
    }
    public async Task InvokeAsync(HttpContext context, ILogger<TestMiddleware> logger)
    {
    logger.LogInformation("中间件开始 logic");
    await _next(context);
    logger.LogInformation("中间件完成 more logic");
    }
    }
  4. 通过依赖注入方法添加中间件:

    新建类 TestMiddleware.cs 注意依赖注入的位置和 3 不同

    public class TestMiddleware : IMiddleware
    {
    private readonly ILogger _logger;
    public TestMiddleware(ILogger<TestMiddleware> logger)
    {
    _logger = logger;
    }
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
    _logger.LogInformation("中间件开始");
    await next(context);
    _logger.LogInformation("中间件完成");
    }
    }

    Startup.cs类的ConfigureServices方法里添加如下代码:

    services.AddTransient<TestMiddleware>();

    Startup.cs类的Configure方法里添加如下代码:

    app.UseMiddleware<TestMiddleware>();
  5. 还有一种第三方容器激活中间件

源代码分析(部分)

  1. RunUse的实现

    直接放出源代码:

     public static void Run(this IApplicationBuilder app, RequestDelegate handler)
    {
    if (app == null)
    {
    throw new ArgumentNullException(nameof(app));
    }
    if (handler == null)
    {
    throw new ArgumentNullException(nameof(handler));
    }
    app.Use(_ => handler);
    }
     public static IApplicationBuilder Use(this IApplicationBuilder app, Func<HttpContext, Func<Task>, Task> middleware)
    {
    return app.Use(next =>
    {
    return context =>
    {
    Func<Task> simpleNext = () => next(context);
    return middleware(context, simpleNext);
    };
    });
    }

    2 个方法最终调用的都是app.Use(),我们看下代码:

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

    _componentsIList<Func<RequestDelegate, RequestDelegate>>类型,其实就是把我们的Middleware添加到 _components 中,继续看代码:

    public RequestDelegate Build()
    {
    RequestDelegate app = context =>
    {
    context.Response.StatusCode = 404;
    return Task.CompletedTask;
    };
    foreach (var component in _components.Reverse())
    {
    app = component(app);
    }
    return app;
    }

    该方法会在Program.csMain方法的 CreateWebHostBuilder(args).Build().Run();Run() 方法执行。

    此方法把我们所有的Middleware再次组装成 1 个新的RequestDelegate,最终的顺序将会是:

    Middleware1()
    {
    next()=>Middleware2()
    {
    next()=>Middleware3()
    {
    next()=>最后的那个返回404的委托
    }
    }
    }

    不知道写清楚了没( ╯□╰ ). 其中next()=>Middleware2()的意思为:next()就是 Middleware2()

  2. 继承 IMiddleware 和没继承 IMiddleware(根据规范必须要有 InvokeAsync 或 Invoke 方法等)的区别:

    按功能实现方面来说是没区别的,但按性能方面应该是继承了 IMiddleware 的方式要好很多,因为没继承 IMiddleware 的方式会用到反射。(未测试,由于继承 IMiddleware 还需要用依赖注入这里只是猜测)

    代码见:

    Microsoft.AspNetCore.Http.Abstractions\Extensions\UseMiddlewareExtensions.cs 的 UseMiddleware 方法。

  3. 未继承 IMiddleware 时的约定,直接看代码吧:

    //1.在middleware中必须存在public且有返回值的方法
    var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public); //2.必须有‘Invoke’或‘InvokeAsync’方法
    var invokeMethods = methods.Where(m =>
    string.Equals(m.Name, "Invoke", StringComparison.Ordinal)
    || string.Equals(m.Name, "InvokeAsync", StringComparison.Ordinal)
    ).ToArray(); //3.‘Invoke’和‘InvokeAsync’只能有1个
    if (invokeMethods.Length > 1) {} //4.‘Invoke’和‘InvokeAsync’必须要存在
    if (invokeMethods.Length == 0) {}
    var methodInfo = invokeMethods[0]; //5.返回结果类型必须为Task
    if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType)){}
  4. 中间件传参

    直接上代码:

    public class RequestTestMiddleware
    {
    private readonly RequestDelegate _next;
    private int _i;
    public RequestTestMiddleware(RequestDelegate next, int i)
    {
    _next = next;
    _i = i;
    }
    public async Task InvokeAsync(HttpContext context, ILogger<TestMiddleware> logger)
    {
    logger.LogInformation($"通过参数传递i值:{_i}");
    logger.LogInformation("中间件开始");
    await _next(context);
    logger.LogInformation("中间件完成");
    }
    }

    Startup.cs类的Configure方法里:

    //参数类型为: params object[] args
    app.UseMiddleware<RequestTestMiddleware>(1);

    具体实现方式同样在 Microsoft.AspNetCore.Http.Abstractions\Extensions\UseMiddlewareExtensions.cs 的 UseMiddleware 方法中

高级用法 Map MapWhen

  1. Map

    app.Map("/map", _app =>
    {
    _app.Run(async context =>
    {
    await context.Response.WriteAsync("Test Map!");
    });
    });

    当访问https://localhost:5001/map时将返回 Test Map!

    这里说一下,代码中并没有 MapController....

  2. MapWhen

    app.MapWhen(context => context.Request.Query.ContainsKey("branch"), _app =>
    {
    _app.Run(async context =>
    {
    await context.Response.WriteAsync("Test Map!");
    });
    });

    看源代码会发现,MapWhen 的第二个参数(委托)并不是上面Use()next(),而是存在MapOptionsBranch属性中,也是RequestDelegate委托

其他说明

  1. Middleware 的执行的有顺序的,在合适的 Middleware 返回请求可时管道更短,速度更快。

    比如 UseStaticFiles(),静态资源不必走验证、MVC 中间件,所以该方法在中间件的前面执行。

  2. 我们看到有很多内置的中间件的用法是*Use**,其实是加了个扩展:

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

总结

  第一次写博客,最大的感触就是慢,然后就是思维逻辑有点混乱,总想用最简单的语言来表达,就是掌握不好。最后看起来还是太啰嗦了点。最后说明,以上很可能有错误的说法,希望大家以批判的角度来看,有任何问题可在留言区留言!Thanks!

ASP.NET Core 中的中间件的更多相关文章

  1. 在Asp.net Core中使用中间件来管理websocket

    介绍 ASP.NET Core SignalR是一个有用的库,可以简化Web应用程序中实时通信的管理.但是,我宁愿使用WebSockets,因为我想要更灵活,并且与任何WebSocket客户端兼容. ...

  2. 在Asp.Net Core中使用中间件保护非公开文件

    在企业开发中,我们经常会遇到由用户上传文件的场景,比如某OA系统中,由用户填写某表单并上传身份证,由身份管理员审查,超级管理员可以查看. 就这样一个场景,用户上传的文件只能有三种人看得见(能够访问) ...

  3. Asp.Net Core 通过自定义中间件防止图片盗链的实例(转)

    一.原理 要实现防盗链,我们就必须先理解盗链的实现原理,提到防盗链的实现原理就不得不从HTTP协议说起,在HTTP协议中,有一个表头字段叫referer,采用URL的格式来表示从哪儿链接到当前的网页或 ...

  4. ASP.NET Core系列:中间件

    1. 概述 ASP.NET Core中的中间件是嵌入到应用管道中用于处理请求和响应的一段代码. 2. 使用 IApplicationBuilder 创建中间件管道 2.1 匿名函数 使用Run, Ma ...

  5. ASP.NET Core 中的那些认证中间件及一些重要知识点

    前言 在读这篇文章之间,建议先看一下我的 ASP.NET Core 之 Identity 入门系列(一,二,三)奠定一下基础. 有关于 Authentication 的知识太广,所以本篇介绍几个在 A ...

  6. [转]ASP.NET Core 中的那些认证中间件及一些重要知识点

    本文转自:http://www.qingruanit.net/c_all/article_6645.html 在读这篇文章之间,建议先看一下我的 ASP.NET Core 之 Identity 入门系 ...

  7. 在ASP.NET Core 中使用Cookie中间件

    在ASP.NET Core 中使用Cookie中间件 ASP.NET Core 提供了Cookie中间件来序列化用户主题到一个加密的Cookie中并且在后来的请求中校验这个Cookie,再现用户并且分 ...

  8. ASP.NET Core中使用GraphQL - 第二章 中间件

    前文:ASP.NET Core中使用GraphQL - 第一章 Hello World 中间件 如果你熟悉ASP.NET Core的中间件,你可能会注意到之前的博客中我们已经使用了一个中间件, app ...

  9. ASP.NET Core 中的SEO优化(2):中间件中渲染Razor视图

    前言 上一篇文章<ASP.NET Core 中的SEO优化(1):中间件实现服务端静态化缓存>中介绍了中间件的使用方法.以及使用中间件实现服务端静态化缓存的功能.本系列文章的这些技巧都是我 ...

随机推荐

  1. TexStudio 非常好用的Latex软件

    先大概写一下,免得忘了,等有时间详细补充. 跨平台.免费. 语法高亮 方便的公式.符号选择界面 可以配置Latex,pdflatex,xelatex等默认编译命令 集成了pdf阅读器,可在阅读器中浏览 ...

  2. 跨域问题解决方案之chrome插件

    地址: https://chrome.google.com/webstore/detail/allow-control-allow-origi/nlfbmbojpeacfghkpbjhddihlkki ...

  3. Java 虚拟机-Java内存区域

    简要介绍Java的内存区域: 运行时数据区域 HotSpot虚拟机对象 一.概览 二.运行时数据区域 2.1 程序计数器 Program Counter Register,代表当前线程所执行的字节码的 ...

  4. kibana 统计field所有值百分比

    步骤: 1.创建新的Visualize——Pie chart 2.工具栏设置 metrics: slice size Count (默认) buckets:Split Slices 注意调整size: ...

  5. MYSQLdump参数详解(转)

    mysqldump客户端可用来转储数据库或搜集数据库进行备份或将数据转移到另一个SQL服务器(不一定是一个MySQL服务器).转储包含创建表和/或装载表的SQL语句. 如果你在服务器上进行备份,并且表 ...

  6. (转)C#正则表达式Regex类的用法

    原文地址如下:http://www.studyofnet.com/news/297.html 一.C#正则表达式符号模式 字 符 描 述 \ 转义字符,将一个具有特殊功能的字符转义为一个普通字符,或反 ...

  7. Dubbo实现RPC调用使用入门

    使用Dubbo进行远程调用实现服务交互,它支持多种协议,如Hessian.HTTP.RMI.Memcached.Redis.Thrift等等.由于Dubbo将这些协议的实现进行了封装了,无论是服务端( ...

  8. AngularJS核心特性(四大点)

    本人刚刚接触AngularJS,还不太熟悉,就说说我目前遇到的一些注意点吧. 1.调用外来文件script文件 AngularJS核心特性一  MVC MVC设计模式 html文件 <!DOCT ...

  9. Mysql 5.6 MHA (gtid) on Kylin

    mha on Kylinip hostname repl role mha role192.168.19.69 mysql1 master node192.168.19.73 mysql2 slave ...

  10. IDEA 运行spingboot时出现Process finished with exit code -1073741819 (0xC0000005)

    经过多方查证,问题最终定位在金山词霸2016上,如果开启了金山词霸的取词和划意功能,就会出现此错误,估计是冲突吧. 解决办法:关掉金山词霸,或者把金山词霸的取词和划意功能关掉.经过尝试,发现只要在ID ...