当前位置: 移动技术网 > IT编程>开发语言>.net > ASP.NET Core 中的中间件

ASP.NET Core 中的中间件

2018年09月19日  | 移动技术网IT编程  | 我要评论

3u8583航班,稀饭网,cz6288

前言

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

何为中间件?

在 asp.net framework 中应该都知道请求管道。可参考: 系列,个人感觉超详细。

题外话:
说到请求管道,就想以前还是超菜鸟时有次面试被问到这个问题,一脸懵逼只说了 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!

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网