音效素材网提供各类素材,打造精品素材网站!

站内导航 站长工具 投稿中心 手机访问

音效素材

ASP.NET Core应用错误处理之ExceptionHandlerMiddleware中间件呈现“定制化错误页面”
日期:2021-09-07 22:41:45   来源:脚本之家

前言

DeveloperExceptionPageMiddleware中间件利用呈现出来的错误页面实现抛出异常和当前请求的详细信息以辅助开发人员更好地进行纠错诊断工作,而ExceptionHandlerMiddleware中间件则是面向最终用户的,我们可以利用它来显示一个友好的定制化的错误页面。按照惯例,我们还是先来看看ExceptionHandlerMiddleware的类型定义。

 public class ExceptionHandlerMiddleware
 { 
 public ExceptionHandlerMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IOptions<ExceptionHandlerOptions> options, DiagnosticSource diagnosticSource); 
 public Task Invoke(HttpContext context);
 }
 
 public class ExceptionHandlerOptions
 {
 public RequestDelegate ExceptionHandler { get; set; }
 public PathString  ExceptionHandlingPath { get; set; }
 }

与DeveloperExceptionPageMiddleware类似,我们在创建一个ExceptionHandlerMiddleware对象的时候同样需要提供一个携带配置选项的对象,从上面的代码可以看出这是一个ExceptionHandlerOptions。具体来说,一个ExceptionHandlerOptions对象通过其ExceptionHandler属性提供了一个最终用来处理请求的RequestDelegate对象。如果希望发生异常后自动重定向到某个指定的路径,我们可以利用ExceptionHandlerOptions对象的ExceptionHandlingPath属性来指定这个路径。我们一般会调用ApplicationBuilder的扩展方法UseExceptionHandler来注册ExceptionHandlerMiddleware中间件,这些重载的UseExceptionHandler方法会采用如下的方式完整中间件的注册工作。

 public static class ExceptionHandlerExtensions
 {
 public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app)=> app.UseMiddleware<ExceptionHandlerMiddleware>();
 
 public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app, ExceptionHandlerOptions options) 
 => app.UseMiddleware<ExceptionHandlerMiddleware>(Options.Create(options));
 
 public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app, string errorHandlingPath)
 { 
  return app.UseExceptionHandler(new  {
  ExceptionHandlingPath = new PathString(errorHandlingPath)
  });
 }
 
 public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app, Action<IApplicationBuilder> configure)
 {
  IApplicationBuilder newBuilder = app.New();
  configure(newBuilder);
 
  return app.UseExceptionHandler(new ExceptionHandlerOptions
  {
  ExceptionHandler = newBuilder.Build()
  });
 } 
 }

一、异常处理器

ExceptionHandlerMiddleware中间件处理请求的本质就是在后续请求处理过程中出现异常的情况下采用注册的异常处理器来处理并响应请求,这个异常处理器就是我们再熟悉不过的RequestDelegate对象。该中间件采用的请求处理逻辑大体上可以通过如下所示的这段代码来体现。

 public class ExceptionHandlerMiddleware
 {
 private RequestDelegate  _next;
 private ExceptionHandlerOptions _options;
 
 public ExceptionHandlerMiddleware(RequestDelegate next, IOptions<ExceptionHandlerOptions> options,…)
 {
  _next  = next;
  _options = options.Value;
  …
 }
 
 public async Task Invoke(HttpContext context)
 {
  try
  {
  await _next(context);
  }
  catch 
  {
  context.Response.StatusCode = 500;
  context.Response.Clear();
  if (_options.ExceptionHandlingPath.HasValue)
  {
   context.Request.Path = _options.ExceptionHandlingPath;
  }
  RequestDelegate handler = _options.ExceptionHandler ?? _next;
  await handler(context);
  }
 }
 }

如上面的代码片段所示,如果后续的请求处理过程中出现异常,ExceptionHandlerMiddleware中间件会利用一个作为异常处理器的RequestDelegate对象来完成最终的请求处理工作。如果在创建ExceptionHandlerMiddleware时提供的ExceptionHandlerOptions携带着这么一个RequestDelegate对象,那么它将作为最终使用的异常处理器,否则作为异常处理器的实际上就是后续的中间件。换句话说,如果我们没有通过ExceptionHandlerOptions显式指定一个异常处理器,ExceptionHandlerMiddleware中间件会在后续管道处理请求抛出异常的情况下将请求再次传递给后续管道。

当ExceptionHandlerMiddleware最终利用异常处理器来处理请求之前,它会对请求做一些前置处理工作,比如它会将响应状态码设置为500,比如清空当前所有响应内容等。如果我们利用ExceptionHandlerOptions的ExceptionHandlingPath属性设置了一个重定向路径,它会将该路径设置为当前请求的路径。除了这些,ExceptionHandlerMiddleware中间件实际上做了一些没有反应在上面这段代码片段中的工作。

二、异常的传递与请求路径的恢复

由于ExceptionHandlerMiddleware中间件总会利用一个作为异常处理器的RequestDelegate对象来完成最终的异常处理工作,为了让后者能够得到抛出的异常,该中间件应该采用某种方式将异常传递给它。除此之外,由于ExceptionHandlerMiddleware中间件会改变当前请求的路径,当整个请求处理完成之后,它必须将请求路径恢复成原始的状态,否则前置的中间件就无法获取到正确的请求路径。

请求处理过程中抛出的异常和原始请求路径的恢复是通过相应的特性完成的。具体来说,传递这两者的特性分别叫做ExceptionHandlerFeature和ExceptionHandlerPathFeature,对应的接口分别为IExceptionHandlerFeature和IExceptionHandlerPathFeature,如下面的代码片段所示,后者继承前者。默认使用的ExceptionHandlerFeature实现了这两个接口。

 public interface IExceptionHandlerFeature
 {
 Exception Error { get; }
 }
 
 public interface IExceptionHandlerPathFeature : IExceptionHandlerFeature
 {
 string Path { get; }
 }
 
 public class ExceptionHandlerFeature : IExceptionHandlerPathFeature, 
 {
 public Exception Error { get; set; }
 public string Path { get; set; }
 }

当ExceptionHandlerMiddleware中间件将代码当前请求的HttpContext传递给请求处理器之前,它会按照如下所示的方式根据抛出的异常的原始的请求路径创建一个ExceptionHandlerFeature对象,该对象最终被添加到HttpContext之上。当整个请求处理流程完全结束之后,ExceptionHandlerMiddleware中间件会借助这个特性得到原始的请求路径,并将其重新应用到当前请求上下文上。

 public class ExceptionHandlerMiddleware
 {
  ...
  public async Task Invoke(HttpContext context)
  {
   try
   {
    await _next(context);
   }
   catch(Exception ex)
   {
    context.Response.StatusCode = 500;
 
    var feature = new ExceptionHandlerFeature()
    {
     Error = ex,
     Path = context.Request.Path,
    };
    context.Features.Set<IExceptionHandlerFeature>(feature);
    context.Features.Set<IExceptionHandlerPathFeature>(feature);
 
    if (_options.ExceptionHandlingPath.HasValue)
    {
     context.Request.Path = _options.ExceptionHandlingPath;
    }
    RequestDelegate handler = _options.ExceptionHandler ?? _next;
 
    try
    {
     await handler(context);
    }
    finally
    {
     context.Request.Path = originalPath;
    }
   }
  }
 }

在具体进行异常处理的时候,我们可以从当前HttpContext中提取这个ExceptionHandlerFeature对象,进而获取抛出的异常和原始的请求路径。如下面的代码所示,我们利用HandleError方法来呈现一个定制的错误页面。在这个方法中,我们正式借助于这个ExceptionHandlerFeature特性得到抛出的异常,并将它的类型、消息以及堆栈追踪显示出来。

 public class Program
 {
  public static void Main()
  {
   new WebHostBuilder()
    .UseKestrel()
    .ConfigureServices(svcs=>svcs.AddRouting())
    .Configure(app => app
     .UseExceptionHandler("/error")
     .UseRouter(builder=>builder.MapRoute("error", HandleError))
     .Run(context=> Task.FromException(new InvalidOperationException("Manually thrown exception"))))
    .Build()
    .Run();
  }
 
  private async static Task HandleError(HttpContext context)
  {
   context.Response.ContentType = "text/html";
   Exception ex = context.Features.Get<IExceptionHandlerPathFeature>().Error;
 
   await context.Response.WriteAsync("<html><head><title>Error</title></head><body>");
   await context.Response.WriteAsync($"<h3>{ex.Message}</h3>");
   await context.Response.WriteAsync($"<p>Type: {ex.GetType().FullName}");
   await context.Response.WriteAsync($"<p>StackTrace: {ex.StackTrace}");
   await context.Response.WriteAsync("</body></html>");
  }

在上面这个应用中,我们注册了一个模板为“error”的路由指向这个HandleError方法。对于通过调用扩展方法UseExceptionHandler注册的ExceptionHandlerMiddleware来说,我们将该路径设置为异常处理路径。那么对于任意从浏览器发出的请求,都会得到如下图所示的错误页面。

三、清除缓存

对于一个用于获取资源的GET请求来说,如果请求目标是一个相对稳定的资源,我们可以采用客户端缓存的方式避免相同资源的频繁获取和传输。对于作为资源提供者的Web应用来说,当它在处理请求的时候,除了将目标资源作为响应的主体内容之外,它还需要设置用于控制缓存的相关响应报头。由于缓存在大部分情况下只适用于成功的响应,如果服务端在处理请求过程中出现异常,之前设置的缓存报头是不应该出现在响应报文中。对于ExceptionHandlerMiddleware中间件来说,清楚缓存报头也是它负责的一项重要工作。

我们同样可以通过一个简单的实例来演示ExceptionHandlerMiddleware中间件针对缓存响应报头的清除。在如下这个应用中,我们将针对请求的处理实现在Invoke方法中,它有50%的可能会抛出异常。不论是返回正常的响应内容还是抛出异常,这个方法都会先设置一个“Cache-Control”的响应报头,并将缓存时间设置为1个小时(“Cache-Control: max-age=3600”)。

 public class Program
 {
  public static void Main()
  {
   new WebHostBuilder()
    .UseKestrel()
    .ConfigureServices(svcs => svcs.AddRouting())
    .Configure(app => app
     .UseExceptionHandler(builder => builder.Run(async context => await context.Response.WriteAsync("Error occurred!")))
     .Run(Invoke))
    .Build()
    .Run();
  }
 
  private static Random _random = new Random();
  private async static Task Invoke(HttpContext context)
  {
   context.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue
   {
    MaxAge = TimeSpan.FromHours(1)
   };
 
   if (_random.Next() % 2 == 0)
   {
    throw new InvalidOperationException("Manually thrown exception...");
   }
   await context.Response.WriteAsync("Succeed...");
  }
 }

通过调用扩展方法 UseExceptionHandler注册的ExceptionHandlerMiddleware中间件在处理异常时会响应一个内容为“Error occurred!”的字符串。如下所示的两个响应报文分别对应于正常响应和抛出异常的情况,我们会发现程序中设置的缓存报头“Cache-Control: max-age=3600”只会出现在状态码为“200 OK”的响应中。至于状态码为“500 Internal Server Error”的响应中,则会出现三个与缓存相关的报头,它们的目的都会为了禁止缓存(或者指示缓存过期)。

 HTTP/1.1 200 OK
 Date: Sat, 17 Dec 2016 14:39:02 GMT
 Server: Kestrel
 Cache-Control: max-age=3600
 Content-Length: 10
 
 Succeed...
 
 
 HTTP/1.1 500 Internal Server Error
 Date: Sat, 17 Dec 2016 14:38:39 GMT
 Server: Kestrel
 Cache-Control: no-cache
 Pragma: no-cache
 Expires: -1
 Content-Length: 15
 
 Error occurred!

ExceptionHandlerMiddleware中间件针对缓存响应报头的清除体现在如下所示的代码片段中。我们可以看出它通过调用HttpResponse的OnStarting方法注册了一个回调(ClearCacheHeaders),上述的这三个缓存报头在这个回调中设置的。除此之外,我们还看到这个回调方法还会清除ETag报头,这也很好理解:由于目标资源没有得到正常的响应,表示资源“签名”的ETag报头自然不应该出现在响应报文中。

 public class ExceptionHandlerMiddleware
 {
  ...
  public async Task Invoke(HttpContext context)
  {
   try
   {
    await _next(context);
   }
   catch (Exception ex)
   {
    …
    context.Response.OnStarting(ClearCacheHeaders, context.Response);
    RequestDelegate handler = _options.ExceptionHandler ?? _next;
    await handler(context);
   }
  }
 
  private Task ClearCacheHeaders(object state)
  {
   var response = (HttpResponse)state;
   response.Headers[HeaderNames.CacheControl]  = "no-cache";
   response.Headers[HeaderNames.Pragma]   = "no-cache";
   response.Headers[HeaderNames.Expires]   = "-1";
   response.Headers.Remove(HeaderNames.ETag);
   return Task.CompletedTask;
  }
 }

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。

    您感兴趣的教程

    在docker中安装mysql详解

    本篇文章主要介绍了在docker中安装mysql详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编...

    详解 安装 docker mysql

    win10中文输入法仅在桌面显示怎么办?

    win10中文输入法仅在桌面显示怎么办?

    win10系统使用搜狗,QQ输入法只有在显示桌面的时候才出来,在使用其他程序输入框里面却只能输入字母数字,win10中...

    win10 中文输入法

    一分钟掌握linux系统目录结构

    这篇文章主要介绍了linux系统目录结构,通过结构图和多张表格了解linux系统目录结构,感兴趣的小伙伴们可以参考一...

    结构 目录 系统 linux

    PHP程序员玩转Linux系列 Linux和Windows安装

    这篇文章主要为大家详细介绍了PHP程序员玩转Linux系列文章,Linux和Windows安装nginx教程,具有一定的参考价值,感兴趣...

    玩转 程序员 安装 系列 PHP

    win10怎么安装杜比音效Doby V4.1 win10安装杜

    第四代杜比®家庭影院®技术包含了一整套协同工作的技术,让PC 发出清晰的环绕声同时第四代杜比家庭影院技术...

    win10杜比音效

    纯CSS实现iOS风格打开关闭选择框功能

    这篇文章主要介绍了纯CSS实现iOS风格打开关闭选择框,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作...

    css ios c

    Win7如何给C盘扩容 Win7系统电脑C盘扩容的办法

    Win7如何给C盘扩容 Win7系统电脑C盘扩容的

    Win7给电脑C盘扩容的办法大家知道吗?当系统分区C盘空间不足时,就需要给它扩容了,如果不管,C盘没有足够的空间...

    Win7 C盘 扩容

    百度推广竞品词的投放策略

    SEM是基于关键词搜索的营销活动。作为推广人员,我们所做的工作,就是打理成千上万的关键词,关注它们的质量度...

    百度推广 竞品词

    Visual Studio Code(vscode) git的使用教程

    这篇文章主要介绍了详解Visual Studio Code(vscode) git的使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。...

    教程 Studio Visual Code git

    七牛云储存创始人分享七牛的创立故事与

    这篇文章主要介绍了七牛云储存创始人分享七牛的创立故事与对Go语言的应用,七牛选用Go语言这门新兴的编程语言进行...

    七牛 Go语言

    Win10预览版Mobile 10547即将发布 9月19日上午

    微软副总裁Gabriel Aul的Twitter透露了 Win10 Mobile预览版10536即将发布,他表示该版本已进入内部慢速版阶段,发布时间目...

    Win10 预览版

    HTML标签meta总结,HTML5 head meta 属性整理

    移动前端开发中添加一些webkit专属的HTML5头部标签,帮助浏览器更好解析HTML代码,更好地将移动web前端页面表现出来...

    移动端html5模拟长按事件的实现方法

    这篇文章主要介绍了移动端html5模拟长按事件的实现方法的相关资料,小编觉得挺不错的,现在分享给大家,也给大家...

    移动端 html5 长按

    HTML常用meta大全(推荐)

    这篇文章主要介绍了HTML常用meta大全(推荐),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参...

    cdr怎么把图片转换成位图? cdr图片转换为位图的教程

    cdr怎么把图片转换成位图? cdr图片转换为

    cdr怎么把图片转换成位图?cdr中插入的图片想要转换成位图,该怎么转换呢?下面我们就来看看cdr图片转换为位图的...

    cdr 图片 位图

    win10系统怎么录屏?win10系统自带录屏详细教程

    win10系统怎么录屏?win10系统自带录屏详细

    当我们是使用win10系统的时候,想要录制电脑上的画面,这时候有人会想到下个第三方软件,其实可以用电脑上的自带...

    win10 系统自带录屏 详细教程

    + 更多教程 +
    ASP编程JSP编程PHP编程.NET编程python编程