在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请求方法。看完这些你再看其他的扩展方法,就会容易理解多啦