每个组件:
- 选择是否将请求传递到管道中的下一个组件。
- 可在管道中的下一个组件前后执行工作。
请求委托处理每个 HTTP 请求。
当中间件短路时,它被称为“终端中间件”,因为它阻止中间件进一步处理请求。
将 HTTP 处理程序和模块迁移到 ASP.NET Core 中间件介绍了 ASP.NET Core 和 ASP.NET 4.x 中请求管道之间的差异,并提供了更多的中间件示例。
中间件顺序
此顺序对于安全性、性能和功能至关重要。
// 运行时调用此方法。使用此方法配置HTTP请求管道。 public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } // 启用swagger中间件 app.UseSwaggerMiddleware(); // 全局异常捕获 app.UseErrorHandlingMiddleware(); //静态文件 app.UseUploadConfig(); // 路由中间件 app.UseRouting(); // 跨域检查 app.UseCors(_allowSpecificOrigins); // 启用多租户中间件(自定义) app.UseMultiTenant(); // (认证中间件)启用Authentication中间件,遍历策略中的身份验证方案获取多张证件,最后合并放入HttpContext.User中 app.UseAuthentication(); // (授权中间件)对请求进行权限验证 app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); //endpoints.MapControllerRoute("default", "{__tenant__=tenant1}/api/{controller=Home}/{action=Index}/{id?}"); //endpoints.MapControllerRoute("default", "{__tenant__=}/api/{controller=Home}/{action=Index}"); }); }
在上述代码中:
- 单个用户帐户创建新的 Web 应用时未添加的中间件已被注释掉。
-
例如:
UseCors、UseAuthentication和UseAuthorization必须按照上述顺序运行。- 此错误,
UseCors当前必须在UseResponseCaching之前运行。
以下 Startup.Configure 方法将为常见应用方案添加中间件组件:
-
异常/错误处理
-
当应用在开发环境中运行时:
- UseDeveloperExceptionPage) 报告应用运行时错误。
- 数据库错误页中间件报告数据库运行时错误。
-
当应用在生产环境中运行时:
- UseExceptionHandler) 捕获以下中间件中引发的异常。
- UseHsts) 添加
Strict-Transport-Security标头。
-
当应用在开发环境中运行时:
- UseHttpsRedirection) 将 HTTP 请求重定向到 HTTPS。
- UseStaticFiles) 返回静态文件,并简化进一步请求处理。
- UseCookiePolicy) 使应用符合欧盟一般数据保护条例 (GDPR) 规定。
- UseRouting)。
- UseAuthentication) 尝试对用户进行身份验证,然后才会允许用户访问安全资源。
- UseAuthorization)。
- 如果应用使用会话状态,请在 Cookie 策略中间件之后和 MVC 中间件之前调用会话中间件。
- UseEndpoints)。
内置中间件
使用 IApplicationBuilder 创建中间件管道部分。
自定义中间件 比如异常中间件
首先,创建一个中间件ExceptionMiddleware
using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace NetLock.Presentation.Api.Middleware { public class ErrorHandlingMiddleware { private readonly RequestDelegate next; private readonly ILogger<ErrorHandlingMiddleware> _logger; public ErrorHandlingMiddleware(RequestDelegate next, ILogger<ErrorHandlingMiddleware> logger) { this.next = next; this._logger = logger; } public async Task InvokeAsync(HttpContext context) { try { await next(context); } catch (Exception ex) { await HandleExceptionAsync(context, ex); } } private Task HandleExceptionAsync(HttpContext context, Exception ex) { //var code = HttpStatusCode.InternalServerError; // 500 if unexpected var code = StatusCodes.Status500InternalServerError; string info = "服务器内部错误,无法完成请求"; if (ex is Exception) { code = 401; info = ex.Message == "" ? "未登录" : ex.Message; } else if (ex is UnAuthorizeException) { code = 401; info = ex.Message == "" ? "无权访问" : ex.Message; } else { switch (context.Response.StatusCode) { case 401: info = "没有权限"; break; case 404: info = "未找到服务"; break; case 403: info = "服务器理解请求客户端的请求,但是拒绝执行此请求"; break; case 500: info = "服务器内部错误,无法完成请求"; break; case 502: info = "请求错误"; break; default: info = ex.Message; break; } } _logger.LogError(info); // todo:可记录日志,如通过注入Nlog等三方日志组件记录 var result = JsonConvert.SerializeObject(new { Coede= code.ToString(), Message = info }); context.Response.ContentType = "application/json"; context.Response.StatusCode = code; return context.Response.WriteAsync(result); } } }
管道的添加顺序决定了它的执行顺序,所以如果您想扩大异常捕获的范围,可以将该管道放置在 Configure 的第一行。 但是!! 您会发现,这个默认的AspNet Core项目不是已经在第一行弄了一个异常处理么?
if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); }
这行代码大家在初始化新AspNetCore项目时就会看到,也有可能您只有上半段,这和模板有关系。不过这都没有关系,它的作用就是捕获和处理异常而已。关于 UseDeveloperExceptionPage 该扩展咱们就不多说了,它的意思是:对于开发模式,一旦报错就会跳转到错误堆栈页面。 而第二个 UseExceptionHandler 就很有意思了,从它命名就可以看出,它肯定是个错误拦截程序。那么它和咱们自定义的异常处理管道有什么区别呢?
“不指定肯定有个默认吧!” 是的,它就是默认的错误处理。所以,它其实也是一个中间件,它的真身叫做 ExceptionHandlerMiddleware。在使用 UseExceptionHandler 方法时,我们可以选填各种参数。比如上方的代码,填入了 "/Error" 参数,表示当产生异常的时候,将定向到对应路径,此处就定位的是: “http://localhost:5001/Error” 。当然您也可以随意指定页面,比如 漂亮的乔殿下页面。????
创建 HandleException(HttpContext context, Exception e) 处理异常,判断是 Development 环境下,输出详细的错误信息,非 Development 环境仅提示调用者“抱歉,出错了”,同样,在 Startup.cs 中将 ExceptionMiddleware 加入管道中
//ExceptionMiddleware 加入管道 app.UseMiddleware<ExceptionMiddleware>();
通过依赖注入和管道中间件两种不同的全局捕获异常处理。实际项目中,也是应当区分不同的业务场景,输出不同的日志信息,不管是从安全或者是用户体验友好性上面来说,都是非常值得推荐的方式,全局异常捕获处理,完全和业务剥离。
IExceptionFilter 作为MVC中间件之间的内容,它需要MVC在发现错误之后将错误信息提交给它处理,因此它的错误处理范围仅限于MVC中间件。所以,假如我们需要捕获MVC中间件之前的一些错误,其实是捕获不到的。 而对于ExceptionHandlerMiddleware中间件来说就很简单了,它作为第一个中间件,凡是在它之后的所有错误它都能够捕获得到。
那么这么看来是否IExceptionFilter就毫无用武之地了呢? 非也,假如您想在MVC发生异常时快速捕获和处理,使用过滤器其实是您不错得选择,如果您仅仅关心控制器之间的异常,那么过滤器也是很好的选择。
还记得刚开始我们在过滤器中说过的这一行代码吗:context.ExceptionHandled = true;。如果在IExceptionFilter中将异常标记为已经处理之后,则第一道异常处理中间件就认为没有错误了,不会进入到处理逻辑中。所以,如果咱们不把该属性改为 true,很有可能出现拦截结果被覆盖的情况。