谈谈ASP.NET CORE核心概念:深入理解中间件及自定义中间件
1. 中间件概念通俗理解
一个web应用程序都是把http请求封装成一个管道,一般来说每一次的请求都要先经过管道的一系列操作,最终到达我们写的代码(主程序)中。那么中间件就是在应用程序管道中的一个组件,用来拦截请求过程进行一些其他处理和响应(比如验证用户身份是否合法、程序中是否有异常等)。中间件可以有很多个,每一个中间件都可以对管道中的请求进行拦截,它可以决定是否将请求转移给下一个中间件。简言之:中间件是用于组成应用程序管道来处理请求和响应的组件。与过滤器类似,都属于AOP应用。
在ASP.NET Core中,针对HTTP请求采用pipeline(管道)方式来处理,而管道容器内可以挂载很多中间件(处理逻辑)“串联”来处理HTTP请求,每一个中间件都有权决定是否需要执行下一个中间件,或者直接做出响应。这样的机制使得HTTP请求能够很好的被层层处理和控制,并且层次清晰处理起来方便。
那么,何时使用中间件呢?一般是在我们的应用程序当中和业务关系不大的一些需要在管道中做的事情可以使用,比如身份验证,Session存储,日志记录等。
微软官方的一个中间件管道请求图:
问题:为何中间件不直接通过一个RequestDelegate对象来表示,而是表示为一个类型为
Func<RequestDelegate, RequestDelegate>的委托对象呢?
答:中间件并不孤立地存在,所有注册的中间件最终会根据注册的先后顺序组成一个链表,
每个中间件不仅仅需要完成各自的请求处理任务外,还需要驱动链表中的下一个中间件。
从上图可以看出除最后一个中间件外,每个中间件包含3部分,一个进入时处理逻辑,一个是调用next方法进入下一个中间件,一个是退出中间件的处理逻辑。
意思即是每个委托均可在下一个委托前后执行操作。
此外,委托还可以决定不将请求传递给下一个委托,这就是对请求管道进行短路。
2. 自定义中间件
每个中间件在构建后就是一个RequestDelegate委托对象。实例:在当前解决方案下创建一个.NET Core mvc项目,命名为MiddlewareDemo,然后在其中创建一个文件夹Middlewares,再在其中创建MyMiddleware类。
public class MyMiddleware
{
//private IConfiguration _configuration;
//第一步:
private RequestDelegate _next;// using Microsoft.AspNetCore.Http;
//加一个构造方法.构造方法第一个参数必须是RequestDelegate类型,表示为中间件类型,即是表示为下一个中间件。定义中间件时必须包含对下一个中间件的引用。
public MyMiddleware(RequestDelegate next)
{
_next = next;//通过私有字段去接收下一个中间件的引用,因为我们在其他地方需要用这个下一个中间件next。这步是关键,必须的有,这个实现把中间件串联起来
//_configuration = configuration;
}
//第二步增加Task InvokeAsync(HttpContext context)方法,方法名称固定为InvokeAsync,返回值为Task
public async Task InvokeAsync(HttpContext context)
{
await context.Response.WriteAsync("test");
await _next.Invoke(context);
}
}
接下来在StartUp.cs文件的Configure方法中注入启用中间件。使用UseMiddleware方法注册:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseMiddleware<MyMiddleware>();
app.UseCookiePolicy();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
注意:
中间件管道在应用程序启动时构建,默认情况下,中间件的执行顺序根据Startup.cs文件中,在public void Configure(IApplicationBuilder app){} 方法中注册的先后顺序执行。
自定义中间件总结:
- 自定义中间件类
- 构造方法带RequestDelegate(下一个中间件的引用) 参数
- 增加Task InvokeAsync(HttpContext context)方法
- 注册启用中间件IApplicationBuilder.UseMiddleware<TMiddleware>()
此外还可以使用 Run、Map 和 Use 扩展方法来注册启用中间件。
app.Use(),IApplicationBuilder接口原生提供,注册等都用它。 Use不会主动短路整个HTTP管道,但是也不会主动调用下一个中间件,必须自行调用await next.Invoke(); 如果不使用这个方法去调用下一个中间件那么Use此时的效果其实和Run是相同的。
app.Run() ,是一个扩展方法,它需要一个RequestDelegate委托,里面包含了Http的上下文信息,没有next参数,因为它总是在管道最后一步执行。Run方法会使得可以使管道短路,顾名思义就是终结管道向下执行不会调用next()委托,所以Run方法最好放在管道的最后来执行。
app.Map(),也是一个扩展方法,类似于MVC的路由,用途一般是一些特殊请求路径的处理。Map可以根据提供的URL来路由中间件 。见下面例子(3)。
(1)如果中间件的逻辑比较简单,可以不去创建一个类,直接用IApplicationBuilder.Use( Func<RequestDelegate,RequestDelegate> middleware)
如:
app.Use(next =>
{
return context =>
{
return Task.CompletedTask;
};
});
(2)终点中间件——Run方法
整个中间件流水线的最后一个中间件,使用IApplicationBuilder.Run注册。
app.Run(context =>
{
return Task.CompletedTask;
});
(3)分支中间件——Map方法
根据决策逻辑确定后续执行路线,分支后不能再合并
//假设app端的请求均已api开头.下面的意思就表示请求是以api开头的就会进入MyMiddleware中间件,后续的中间件就不会执行。 .Map方法以路径作为决策
app.Map("/api", builder => {
builder.UseMiddleware<MyMiddleware>();
});
测试,在默认地址后加入/api就会调用MyMiddleware中间件,从而显示test。
其实我们的 asp.net core项目中本身已经包含了很多个中间件。
如果想添加使用更多的中间件,通过NuGet包管理器引用安装Microsoft.AspNetCore.Diagnostics
修改 Startup 类中的 Configure() 方法添加中间件 app.UseWelcomePage
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
//上面的代码在非开发环境时,UseExceptionHandler是第一个被加入到管道中的中间件,因为会捕获之后调用中出现的任何异常,然后跳转到设置的异常页/home/Error
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
//
app.UseWelcomePage();
app.UseCookiePolicy();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
运行效果如下: