Asp.Net Core 中间件应用实践中你不知道的那些事
一、概述
这篇文章主要分享Endpoint
终结点路由的中间件
的应用场景及实践案例,不讲述其工作原理,如果需要了解工作原理的同学,
可以点击查看以下两篇解读文章:
1.1 中间件(Middleware)的作用
我们知道,任何的一个web框架都是把http请求封装成一个管道,每一次的请求都是经过管道的一系列操作,最终到达我们写的代码中。那么中间件就是在应用程序管道中的一个组件,用来拦截请求过程进行一些其他处理和响应。中间件可以有很多个,每一个中间件都可以对管道中的请求进行拦截,它可以决定是否将请求转移给下一个中间件。
asp.net core 提供了IApplicationBuilder
接口来让把中间件注册到asp.net的管道请求当中去,中间件是一个典型的AOP
应用。 下面是一个微软官方的一个中间件管道请求图:
1.2 中间件和过滤器的区别
Filter
是延续ASP.NET MVC的产物,同样保留了五种的Filter,分别是Authorization Filter、Resource Filter、Action Filter、Exception Filter及Result Filter。
具体可以查看我上次分享的一篇Asp.Net Core Filter 深入浅出的那些事-AOP 的文章.
根据描述,可以看出中间件和过滤器的功能类似,那么他们有什么区别?为什么又要搞一个中间件呢?
其实,过滤器和中间件他们的关注点是不一样的,也就是说职责不一样,干的事情就不一样。
同作为两个AOP
利器,Filter
(过滤器)更贴合业务,它关注于应用程序本身,比如你看ActionFilter
和 ResultFilter
,它都直接和你的Action
,ActionResult
交互了,是不是离你很近的感觉,那我有一些比如对我的输出结果进行格式化,对我的请求的ViewModel进行数据验证啦,肯定就是用Filter无疑了。它是MVC的一部分,它可以拦截到你Action上下文的一些信息,而中间件是没有这个能力的。
可以看到,每一个中间件都都可以在请求之前和之后进行操作。请求处理完成之后传递给下一个请求
1.3 中间件的使用场景
那么,何时使用中间件呢?我的理解是在我们的应用程序当中和业务关系不大的一些需要在管道中做的事情可以使用,比如身份验证,Session存储,日志记录等。其实我们的 Asp.net core项目中本身已经包含了很多个中间件。比如 身份认证中间件 UseAuthorization()
等系列.
二、中间件实战
需求场景:通过后端记录每一次的访问请求日志,同时需要根据需要排除一些Controller
或者Action
不记录请求的日志信息。
思考:经过分析我需要创建一个全局的中间件进行拦截路由,并且写入日志;同时需要添加一个特性Attribute
进行标注那些Controller
或者Action
不需要进行日志记录。
我们来创建LogsMiddleware
中间件代码,代码如下:
public class LogsMiddleware
{
private readonly RequestDelegate _next;
public LogsMiddleware(RequestDelegate next)
{
this._next = next;
}
public async Task Invoke(HttpContext context)
{
var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;
if (endpoint == null)
{
await _next(context);
return;
}
using (var scope = context.RequestServices.CreateScope())
{
var _logger = scope.ServiceProvider.GetService<ILogger<LogsMiddleware>>();
var attruibutes = endpoint.Metadata.OfType<NoLogsAttriteFilter>();
if (attruibutes.Count()==0)
{
_logger.LogInformation($" url:{context.Request.Path}, 访问时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
}
//记录 排除的特殊Message 信息
foreach (var attribute in attruibutes)
{
_logger.LogInformation(attribute.Message);
}
}
await _next(context);
}
}
NoLogsAttriteFilter
过滤器代码如下:
public class NoLogsAttriteFilter : Attribute
{
/// <summary>
/// 这里加这个主要是把获取到的信息在中间件中打印出来,区分中间件的拦截用处
/// </summary>
public string Message = "";
public NoLogsAttriteFilter(string message)
{
Message = message;
}
}
Startup
中的代码如下:
public void Configure(IApplicationBuilder app, IWebHostEnvironment 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.UseRouting();
app.UseAuthorization();
app.UseMiddleware<LogsMiddleware>();//添加日志记录中间件
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
HomeController
控制器中的两个Action 代码如下::
// 访问该路由会记录访问日志
public IActionResult Index()
{
return View();
}
//访问该路由不会记录访问日志
[NoLogsAttriteFilter("Manage 不需要记录访问日志")]
public IActionResult Manage()
{
return View();
}
这样就自定义日志中间件就已经完成了我上面的需求,不依赖于任何业务独立存在于系统中;从代码中我们可以看到中间件通过context.Features.Get<IEndpointFeature>()?.Endpoint;
方法获得终结点路由方式进行匹配,并且可以通过endpoint.Metadata.OfType<NoLogsAttriteFilter>()
方式获得Action
中的特性信息数据,并通过该拦截进行我的需求
自定义中间件教程文章请点击自定义中间件官方教程 一文。
现在我们再来印证下我上一篇关于 Asp.Net Core EndPoint 终结点路由工作原理解读 一文 中提及到UseRouting()
中间件是遍历所有的Endpoint
终结点路由以匹配当前请求的 Endpoint
终结点路由一说,我把注册LogsMiddleware
中间件和UseRouting()
路由中间件代码顺序调整一下,代码如下:
public void Configure(IApplicationBuilder app, IWebHostEnvironment 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();
}
// 中间件注册放到了UseRouting() 之前
app.UseMiddleware<LogsMiddleware>();//添加日志记录中间件
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
再来看看运行调试的结果如图:
从调试的结果图中可以看出 endpoint
变量是 null;所有需要使用到Endpoint
终结点路由必须注册在UseRouting()
中间件之后。
三、官方常用中间件
- 异常/错误处理
当应用在开发环境中运行时:
开发人员异常页中间件 (UseDeveloperExceptionPage
) 报告应用运行时错误。
数据库错误页中间件报告数据库运行时错误。
当应用在生产环境中运行时:
异常处理程序中间件 (UseExceptionHandler
) 捕获以下中间件中引发的异常。
HTTP 严格传输安全协议 (HSTS) 中间件 (UseHsts) 添加 Strict-Transport-Security 标头。 - HTTPS 重定向中间件 (
UseHttpsRedirection
) 将 HTTP 请求重定向到 HTTPS。 - 静态文件中间件 (
UseStaticFiles
) 返回静态文件,并简化进一步请求处理。 - Cookie 策略中间件 (
UseCookiePolicy
) 使应用符合欧盟一般数据保护条例 (GDPR) 规定。 - 用于路由请求的路由中间件 (
UseRouting
)。 - 身份验证中间件 (
UseAuthentication
) 尝试对用户进行身份验证,然后才会允许用户访问安全资源。 - 用于授权用户访问安全资源的授权中间件 (
UseAuthorization
)。 - 会话中间件 (
UseSession
) 建立和维护会话状态。 如果应用使用会话状态,请在 Cookie 策略中间件之后和 MVC 中间件之前调用会话中间件。 - 用于将
Razor Pages
终结点添加到请求管道的终结点路由中间件(带有 MapRazorPages 的UseEndpoints
)。