ASP.NET Core 中间件 自定义全局异常中间件以及 MVC异常过滤器作用

中间件是一种装配到应用管道以处理请求和响应的软件。 每个组件:

  • 选择是否将请求传递到管道中的下一个组件。
  • 可在管道中的下一个组件前后执行工作。

请求委托用于生成请求管道。 请求委托处理每个 HTTP 请求。

使用 RunMap 和 Use 扩展方法来配置请求委托。 可将一个单独的请求委托并行指定为匿名方法(称为并行中间件),或在可重用的类中对其进行定义。 这些可重用的类和并行匿名方法即为中间件,也叫中间件组件。 请求管道中的每个中间件组件负责调用管道中的下一个组件,或使管道短路。 当中间件短路时,它被称为“终端中间件”,因为它阻止中间件进一步处理请求。

将 HTTP 处理程序和模块迁移到 ASP.NET Core 中间件介绍了 ASP.NET Core 和 ASP.NET 4.x 中请求管道之间的差异,并提供了更多的中间件示例。

中间件顺序

 

 

 

 

 

 向 Startup.Configure 方法添加中间件组件的顺序定义了针对请求调用这些组件的顺序,以及响应的相反顺序。 此顺序对于安全性、性能和功能至关重要。

 // 运行时调用此方法。使用此方法配置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 应用时未添加的中间件已被注释掉。
  • 并非所有中间件都需要准确按照此顺序运行,但许多中间件必须遵循这个顺序。 例如:
    • UseCorsUseAuthentication 和 UseAuthorization 必须按照上述顺序运行。
    • 由于此错误UseCors 当前必须在 UseResponseCaching 之前运行。

以下 Startup.Configure 方法将为常见应用方案添加中间件组件:

  1. 异常/错误处理
    • 当应用在开发环境中运行时:
      • 开发人员异常页中间件 (UseDeveloperExceptionPage) 报告应用运行时错误。
      • 数据库错误页中间件报告数据库运行时错误。
    • 当应用在生产环境中运行时:
      • 异常处理程序中间件 (UseExceptionHandler) 捕获以下中间件中引发的异常。
      • HTTP 严格传输安全协议 (HSTS) 中间件 (UseHsts) 添加 Strict-Transport-Security 标头。
  2. HTTPS 重定向中间件 (UseHttpsRedirection) 将 HTTP 请求重定向到 HTTPS。
  3. 静态文件中间件 (UseStaticFiles) 返回静态文件,并简化进一步请求处理。
  4. Cookie 策略中间件 (UseCookiePolicy) 使应用符合欧盟一般数据保护条例 (GDPR) 规定。
  5. 用于路由请求的路由中间件 (UseRouting)。
  6. 身份验证中间件 (UseAuthentication) 尝试对用户进行身份验证,然后才会允许用户访问安全资源。
  7. 用于授权用户访问安全资源的授权中间件 (UseAuthorization)。
  8. 会话中间件 (UseSession) 建立和维护会话状态。 如果应用使用会话状态,请在 Cookie 策略中间件之后和 MVC 中间件之前调用会话中间件。
  9. 用于将 Razor Pages 终结点添加到请求管道的终结点路由中间件(带有 MapRazorPages 的 UseEndpoints)。

内置中间件

ASP.NET Core 附带以下中间件组件。 “顺序”列提供备注,以说明中间件在请求处理管道中的放置,以及中间件可能会终止请求处理的条件。 如果中间件让请求处理管道短路,并阻止下游中间件进一步处理请求,它被称为“终端中间件”。 若要详细了解短路,请参阅使用 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,很有可能出现拦截结果被覆盖的情况。

 

posted @ 2020-08-26 15:21  netlock  阅读(610)  评论(0编辑  收藏  举报