Asp.Net Core 系列教程 (四) 消息中间件

在ASP.Net Core 添加了诸多的“中间件”处理用户的请求和响应,这种方式非常编译功能扩展,如果我需要修改某个功能时 直接添加对应的中间件就好了,不需要 大规模的调整架构了。

当然再好的架构都有其弊端。

 

矛盾存在于一切事物,并贯穿于事物发展的始终。

 好了,言归正传


消息中间件的原理

先看下面的代码

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseStaticFiles();
            app.UseStatusCodePages();
            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGet("/", async context =>
                {
                    await context.Response.WriteAsync("Hello World!");
                });
            });
        }

这段代码很常见,依次注入了使用静态文件,是错状态码页,使用路由,使用节点,那么这些都是如何实现的呢?

我们先看一下原理,搞懂原理再撸代码。

如下是个简化的管道

 

看完上面的图片,再看代码我想 你也就能明白编写各个Use时顺序为啥是异常处理在最前面了吧。

越是靠前,请求越是先运行,越是靠前,应答越是靠后处理。

因果关系是客观事物本身的一种规律。


如何编写自己的中间件

在MVC中如果我们要在管道中添加一个中间件,可以使用Use方法,该方法是MVC有限的共有方法中的一个,你应该可以发现这个Run方法是多么重要。

在事物发展的任何阶段上,必有而且只有一种矛盾居于支配的地位,起着规定或影响其他矛盾的作用。这种矛盾就是主要矛盾。其他矛盾则是非主要矛盾,即次要矛盾。

我们先看一下Use方法。

        //
        // 摘要:
        //     Adds a middleware delegate to the application's request pipeline.
        //
        // 参数:
        //   middleware:
        //     The middleware delegate.
        //
        // 返回结果:
        //     The Microsoft.AspNetCore.Builder.IApplicationBuilder.
        IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);

这个方法只有一个参数,这个参数 是个带返回值的委托,第一个委托参数 你可以理解为HttpContext上下文(也就是被处理就是对象),第二个委托参数就是管道中的下一个消息中间件,我们调用时不能直接return掉。为啥?如果你直接return掉 后面的消息中间件就不会执行了,任务直接凉凉。

我们应该这样写。

                 app.Use(async (current, next) =>
              {
                  if (current.Request.Headers.ContainsKey("CurrentDateTime"))
                  {
                      current.Response.Headers.Add("DateTime", DateTime.Now.ToString());
                  }
                  await next.Invoke();
              });           

当我们检测到Http上下文请求中包含头CurrentDateTime,就在返回的头中添加 当前时间,最后我们直接await 下一个 中间件。这里不是 retun切记。当然这种写法 在.net 3.0中 已经不推荐了(微软内部还是这样实现的,只是不推荐你这样写)。

小伙子 你不推荐我这样写,那我怎么写?

你应该这样写。

    public class DateTimeMiddleware
    {
        private readonly RequestDelegate next;

        public DateTimeMiddleware(RequestDelegate next)
        {
            this.next = next;
        }
        public async Task Inboke(HttpContext httpContext)
        {
            if (httpContext.Request.Headers.ContainsKey("CurrentDateTime"))
            {
                httpContext.Response.Headers.Add("DateTime", DateTime.Now.ToString());
            }
            await next.Invoke(httpContext);
        }
    }

那么这个类如何调用呢?当然这里不能直接使用Use了,很明显没法传入,怎么搞?使用微软编写的扩展方法

            app.UseMiddleware<DateTimeMiddleware>();

这个扩展方法也是扩展的Use,你也可以自己写一个。为啥要这样写???因为这样方便做单元测试,而你直接写Use那其实是一个匿名方法,不太好做单元测试。


要想详细了解中间件,我们还不得不了解一下分支管道,所谓分支管道就是指满足一定条件才会触发的管道,我们前面说的管道其实无论是否满足条件是都会执行一遍的,好,接下来,我们说下分支管道。

在Asp.Net Core中,分支管道是通过Map来实现的。 

原理很简单,框架是通过识别PathString来识别,然后条件注入IApplicationBuilder。我们具体看一下这个方法。

IApplicationBuilder Map(this IApplicationBuilder app, PathString pathMatch, Action<IApplicationBuilder> configuration);

 依据上面说的,我们就可以把前面写的中间件改成如下格式

            app.Map("/dateTime", appbuilder =>
            {
                appbuilder.UseMiddleware<DateTimeMiddleware>();
            });

 当HttpContext请求上下文Uri中包含dateTime,该消息中间件便会被触发。

发展的实质是新事物产生和旧事物灭亡,发展的本质是事物不断实现自身的“扬弃”向着更高的层次不断前进

 上面说的管道实际上 进入是完全判断dateTime的,但是如果我们还想附加一些条件怎么办?那就使用MapWhen()

基于上面的介绍再看如下的代码,我想你肯定能搞懂如何使用了。

IApplicationBuilder MapWhen(this IApplicationBuilder app, Func<HttpContext, bool> predicate, Action<IApplicationBuilder> configuration);

 其实他不是调用PathString了,而是传入一个HTTPContext,你给他返回一个bool结果,然后框架根据返回值自动决定是否运行后面的代码。如此扩展性强多了。

在Asp.Net Core3.0中(我不是很确定之前是否存在)微软又扩展出了一个新的中间件,

UseEndpoints(this IApplicationBuilder builder, Action<IEndpointRouteBuilder> configure);

这个中间件使用前必须调用

public static IApplicationBuilder UseRouting(this IApplicationBuilder builder);

你可以把他俩理解为一对鸳鸯。

所以我们编写的代码应该类似于这样

            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGet("/", async context =>
                {
                    await context.Response.WriteAsync("Hello World!");
                });
            });

嗯?怎么这里又出现一个Map????啥????这个和app直接map有啥区别??

其实本质上没啥区别,这里的map 匹配的是路由字符串。Look at this!

        //
        // 摘要:
        //     Adds a Microsoft.AspNetCore.Routing.RouteEndpoint to the Microsoft.AspNetCore.Routing.IEndpointRouteBuilder
        //     that matches HTTP requests for the specified pattern.
        //
        // 参数:
        //   endpoints:
        //     The Microsoft.AspNetCore.Routing.IEndpointRouteBuilder to add the route to.
        //
        //   pattern:
        //     The route pattern.
        //
        //   requestDelegate:
        //     The delegate executed when the endpoint is matched.
        //
        // 返回结果:
        //     A Microsoft.AspNetCore.Builder.IEndpointConventionBuilder that can be used to
        //     further customize the endpoint.
        public static IEndpointConventionBuilder Map(this IEndpointRouteBuilder endpoints, RoutePattern pattern, RequestDelegate requestDelegate);
        //
        // 摘要:
        //     Adds a Microsoft.AspNetCore.Routing.RouteEndpoint to the Microsoft.AspNetCore.Routing.IEndpointRouteBuilder
        //     that matches HTTP requests for the specified pattern.
        //
        // 参数:
        //   endpoints:
        //     The Microsoft.AspNetCore.Routing.IEndpointRouteBuilder to add the route to.
        //
        //   pattern:
        //     The route pattern.
        //
        //   requestDelegate:
        //     The delegate executed when the endpoint is matched.
        //
        // 返回结果:
        //     A Microsoft.AspNetCore.Builder.IEndpointConventionBuilder that can be used to
        //     further customize the endpoint.
        public static IEndpointConventionBuilder Map(this IEndpointRouteBuilder endpoints, string pattern, RequestDelegate requestDelegate);

第一个参数换成了RoutePattern,哦吼!这不就是之前老版使用的路由嘛!

微软还在此基础上扩展出了如下方法。


        public static IEndpointConventionBuilder Map(this IEndpointRouteBuilder endpoints, string pattern, RequestDelegate requestDelegate);
        
        public static IEndpointConventionBuilder MapDelete(this IEndpointRouteBuilder endpoints, string pattern, RequestDelegate requestDelegate);
        
        public static IEndpointConventionBuilder MapGet(this IEndpointRouteBuilder endpoints, string pattern, RequestDelegate requestDelegate);
        
        public static IEndpointConventionBuilder MapMethods(this IEndpointRouteBuilder endpoints, string pattern, IEnumerable<string> httpMethods, RequestDelegate requestDelegate);
        
        public static IEndpointConventionBuilder MapPost(this IEndpointRouteBuilder endpoints, string pattern, RequestDelegate requestDelegate);
      
        public static IEndpointConventionBuilder MapPut(this IEndpointRouteBuilder endpoints, string pattern, RequestDelegate requestDelegate);

类似,只不过区分了Http请求方法。看完这些你再看其他的扩展方法,就会容易理解多啦

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值