一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - ASP.NET教程 - ASP.NET Core應用錯誤處理之StatusCodePagesMiddleware中間件針對響應碼呈現錯誤頁面

ASP.NET Core應用錯誤處理之StatusCodePagesMiddleware中間件針對響應碼呈現錯誤頁面

2020-06-06 16:12大內老A ASP.NET教程

這篇文章主要給大家介紹了關于ASP.NET Core應用錯誤處理之StatusCodePagesMiddleware中間件針對響應碼呈現錯誤頁面的相關資料,需要的朋友可以參考下

前言

StatusCodePagesMiddleware中間件與ExceptionHandlerMiddleware中間件比較類似,它們都是在后續請求處理過程中“出錯”的情況下利用一個錯誤處理器來完成最終的請求處理與響應的任務。它們之間的差異在于對“錯誤”的界定上,對于ExceptionHandlerMiddleware中間件來說,它所謂的錯誤就是拋出異常,但是對于StatusCodePagesMiddleware中間件來說,則將介于400~599之間的響應狀態碼視為錯誤。如下面的代碼片段所示,StatusCodePagesMiddleware中間件也采用“標準”的定義方式,針對它的配置選項通過一個對應的對象以Options模式的形式提供給它。

?
1
2
3
4
5
public class StatusCodePagesMiddleware
{
public StatusCodePagesMiddleware(RequestDelegate next, IOptions<StatusCodePagesOptions> options);
public Task Invoke(HttpContext context);
}

除了針對錯誤的界定,StatusCodePagesMiddleware和ExceptionHandlerMiddleware這兩個中間件對于錯誤處理器的表達也不相同。我們知道ExceptionHandlerMiddleware中間件使用的錯誤處理器實際上就是一個類型為RequestDelegate的委托對象,但是錯誤處理器之于StatusCodePagesMiddleware中間件來說則是一個類型為Func<StatusCodeContext, Task>的委托對象。如下面的代碼片段所示,為StatusCodePagesMiddleware中間件提供配置選項的StatusCodePagesOptions對象的唯一目的就是提供這個作為錯誤處理器的委托對象。

?
1
2
3
4
public class StatusCodePagesOptions
{
public Func<StatusCodeContext, Task> HandleAsync { get; set; }
}

我們知道一個RequestDelegate對象相當于一個類型為Func<HttpContext, Task>類型的委托對象,而一個StatusCodeContext對象實際上也是對一個HttpContext對象的封裝,所以StatusCodePagesMiddleware中間件和ExceptionHandlerMiddleware中間件所使采用的錯誤處理器并沒有本質上的不同。如下面的代碼片段所示,除了從StatusCodeContext對象中獲取代表當前請求上下文的HttpContext對象之外,我們還可以通過其Next屬性得到一個RequestDelegate對象,它代表由后續中間件組成的請求處理管道。至于另一個屬性Options,很明顯它返回我們在創建StatusCodePagesMiddleware中間件所指定的StatusCodePagesOptions對象。

?
1
2
3
4
5
6
7
8
public class StatusCodeContext
{
public HttpContext  HttpContext { get; }
public RequestDelegate  Next { get; }
public StatusCodePagesOptions Options { get; }
 
public StatusCodeContext(HttpContext context, StatusCodePagesOptions options, RequestDelegate next);
}

一、針對響應狀態碼的錯誤處理

由于采用了針對響應狀態碼的錯誤處理策略,所以實現在StatusCodePagesMiddleware中間件中的所有錯誤處理操作只會發生在當前響應狀態碼在400~599之間的情況,如下所示的代碼片段體現了這一點。從下面給出的代碼片段可以看出,StatusCodePagesMiddleware中間件在決定是否執行錯誤處理操作時除了會查看當前響應狀態碼之外,還會查看響應內容以及媒體類型,如果已經包含了響應內容或者設置了媒體類型,該中間件將不會執行任何操作。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class StatusCodePagesMiddleware
{
 private RequestDelegate   _next;
 private StatusCodePagesOptions  _options;
 
public StatusCodePagesMiddleware(RequestDelegate next,
 IOptions<StatusCodePagesOptions> options)
 {
  _next   = next;
  _options  = options.Value;
 }
 public async Task Invoke(HttpContext context)
 {  
  await _next(context);
  var response = context.Response;
  if ((response.StatusCode >= 400 && response.StatusCode <= 599) &&!response.ContentLength.HasValue && string.IsNullOrEmpty(response.ContentType))
  {
   await _options.HandleAsync(new StatusCodeContext(context, _options, _next));
  }
 }
}

StatusCodePagesMiddleware中間件針對錯誤的處理非常簡單,它只需要從StatusCodePagesOptions對象中提取出作為錯誤處理器的這個Func<StatusCodeContext, Task>對象,然后創建一個StatusCodeContext對象作為輸入參數調用這個委托對象即可。

二、阻止異常處理

如果當前響應已經被寫入了內容,或者響應的媒體類型已經被預先設置,那么StatusCodePagesMiddleware中間件將不會再執行任何的錯誤處理操作。這種情況實際上代表由后續中間件構成的管道可能需要自行控制當前的響應,所以StatusCodePagesMiddleware中間件不應該再做任何的干預。從這個意義上來講,StatusCodePagesMiddleware中間件僅僅是作為一種后備的錯誤處理機制而已。

更進一步來將,如果后續的某個中間件返回了一個狀態碼在400~599之間的響應,并且這個響應只有報頭集合沒有主體(媒體類型自然也不會設置),那么按照我們在上面給出的錯誤處理邏輯,StatusCodePagesMiddleware中間件還是會按照自己的策略來處理并響應請求。為了解決這種情況下,我們必須賦予后續中間件一個能夠阻止StatusCodePagesMiddleware中間件進行錯誤處理的能力。

阻止StatusCodePagesMiddleware中間件進行錯誤處理的機制是借助于一個名為StatusCodePagesFeature的特性來實現的。StatusCodePagesFeature對應如下這個IStatusCodePagesFeature接口,它具有唯一的布爾類型的屬性成員Enabled。默認使用的StatusCodePagesFeature類型實現了這個接口,默認情況下這個開關是開啟的。

?
1
2
3
4
5
6
7
8
9
public interface IStatusCodePagesFeature
{
 bool Enabled { get; set; }
}
 
public class StatusCodePagesFeature : IStatusCodePagesFeature
{
 public bool Enabled { get; set; } = true ;
}

StatusCodePagesMiddleware中間件在將請求交付給后續管道之前,它會創建一個StatusCodePagesFeature特性對象并將其添加到當前HttpContext之中。當最終決定是否執行錯誤處理操作的時候,它還會通過這個特性檢驗是否某個后續的中間件不希望自己“畫蛇添足”地進行不必要的錯誤處理,如下的代碼片段很好的體現了這一點。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class StatusCodePagesMiddleware
{
 
 public async Task Invoke(HttpContext context)
 {
  StatusCodePagesFeature feature = new StatusCodePagesFeature();
  context.Features.Set<IStatusCodePagesFeature>(feature);
 
  await _next(context);
  var response = context.Response;
  if ((response.StatusCode >= 400 && response.StatusCode <= 599) && !response.ContentLength.HasValue &&string.IsNullOrEmpty(response.ContentType) &&
   feature.Enabled)
  {
   await _options.HandleAsync(new StatusCodeContext(context, _options, _next));
  }
 }
}

我們通過一個簡單的實例來演示如果利用這個StatusCodePagesFeature特性來屏蔽StatusCodePagesMiddleware中間件。在下面這個應用中,我們將針對請求的處理定義在Invoke方法中,該方法會返回一個狀態碼為“401 Unauthorized”的響應。我們通過隨機數讓這個方法會在50%的情況下利用StatusCodePagesFeature特性來阻止StatusCodePagesMiddleware中間件自身對錯誤的處理。我們通過調用擴展方法UseStatusCodePages注冊的StatusCodePagesMiddleware中間件會直接響應一個內容為“Error occurred!”的字符串。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Program
{
 public static void Main()
 {
  new WebHostBuilder()
   .UseKestrel()
   .Configure(app => app
    .UseStatusCodePages(async context => await context.HttpContext.Response.WriteAsync("Error occurred!"))
    .Run(Invoke))
   .Build()
   .Run();
 }
 
 private static Random _random = new Random();
 private static Task Invoke(HttpContext context)
 {
  context.Response.StatusCode = 401;
 
  if (_random.Next() % 2 == 0)
  {
   context.Features.Get<IStatusCodePagesFeature>().Enabled = false;
  }
  return Task.CompletedTask;
 }
}

對于針對該應用的請求來說,我們會得到如下兩種不同的響應。沒有主體內容響應是通過Invoke方法產生的,這種情況下發生在StatusCodePagesMiddleware中間件通過StatusCodePagesFeature特性被屏蔽的時候。具有主體內容的響應則來源于StatusCodePagesMiddleware中間件。

?
1
2
3
4
5
6
7
8
9
10
11
12
HTTP/1.1 401 Unauthorized
Date: Sun, 18 Dec 2016 01:59:37 GMT
Server: Kestrel
Content-Length: 15
 
Error occurred!
 
 
HTTP/1.1 401 Unauthorized
Date: Sun, 18 Dec 2016 01:59:38 GMT
Content-Length: 0
Server: Kestrel

三、注冊StatusCodePagesMiddleware中間件

我們在大部分情況下都會調用ApplicationBuilder相應的擴展方法來注冊StatusCodePagesMiddleware中間件。對于StatusCodePagesMiddleware中間件的注冊來說,除了我們已經很熟悉的UseStatusCodePages方之外,還具有額外一些擴展方法供我們選擇。

UseStatusCodePages

我們可以調用如下三個UseStatusCodePages方法重載來注冊StatusCodePagesMiddleware中間件。不論我們調用那個重載,系統最終都會根據提供的StatusCodePagesOptions對象調用構造函數來創建這個中間件對象,而且這個StatusCodePagesOptions必須具有一個作為錯誤處理器的Func<StatusCodeContext, Task>對象。如果沒有指定任何參數,StatusCodePagesOptions對象需要以Options模式的形式注冊為服務。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static class StatusCodePagesExtensions
{
 public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app)
 
  return app.UseMiddleware<StatusCodePagesMiddleware>();
 }
 
 public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app, StatusCodePagesOptions options)
 {
  return app.UseMiddleware<StatusCodePagesMiddleware>(Options.Create(options));
 }
 
 public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app, Func<StatusCodeContext, Task> handler)
 
  return app.UseStatusCodePages(new StatusCodePagesOptions
  {
   HandleAsync = handler
  });
 }
}

由于StatusCodePagesMiddleware中間件最終的目的還是將定制的錯誤信息響應給客戶端,所以我們可以在注冊該中間件的時候直接指定響應的內容和媒體類型,這樣的注冊方式可以通過調用如下這個UseStatusCodePages方法來完成。從如下所示的代碼片段我們不難看出,我們通過bodyFormat方法指定的實際上是一個模板,它可以包含一個表示響應狀態的占位符(“{0}”)。

?
1
2
3
4
5
6
7
8
9
10
11
12
public static class StatusCodePagesExtensions
{
 public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app, string contentType, string bodyFormat)
 {
  return app.UseStatusCodePages(context =>
  {
   var body = string.Format(CultureInfo.InvariantCulture, bodyFormat, context.HttpContext.Response.StatusCode);
   context.HttpContext.Response.ContentType = contentType;
   return context.HttpContext.Response.WriteAsync(body);
  });
 }
}

UseStatusCodePagesWithRedirects

如果我們調用UseStatusCodePagesWithRedirects方法,可以讓注冊的StatusCodePagesMiddleware中間件向指定的路徑發送一個客戶端重定向。從如下所示的實現代碼可以看出,這個作為參數locationFormat的重定向地址也是一個模板,它可以包含一個表示響應狀態的占位符(“{0}”)。我們可以指定一個完整的地址,也可以指定一個相對于PathBase的相對路徑,后者需要包含表示基地址的“~/”前綴。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static class StatusCodePagesExtensions
 public static IApplicationBuilder UseStatusCodePagesWithRedirects(this IApplicationBuilder app, string locationFormat)
 {
  if (locationFormat.StartsWith("~"))
  {
   locationFormat = locationFormat.Substring(1);
   return app.UseStatusCodePages(context =>
   {
    var location = string.Format(CultureInfo.InvariantCulture, locationFormat, context.HttpContext.Response.StatusCode);
    context.HttpContext.Response.Redirect(context.HttpContext.Request.PathBase + location);
    return Task.CompletedTask;
  });
  }
  else
  {
   return app.UseStatusCodePages(context =>
   {
    var location = string.Format(CultureInfo.InvariantCulture, locationFormat, context.HttpContext.Response.StatusCode);
    context.HttpContext.Response.Redirect(location);
    return Task.CompletedTask;
   });
  }
 }
}

我們通過一個簡單的應用來演示針對客戶端重定向的錯誤頁面呈現方式。我們在如下這個應用中注冊了一個路由模板為“error/{statuscode}”的路由,路由參數“statuscode”自然代表響應的狀態碼。在作為路由處理器的HandleError方法中,我們會直接響應一個包含響應狀態碼的字符串。我們調用UseStatusCodePagesWithRedirects方法注冊StatusCodePagesMiddleware中間件的時候將重定義路徑設置為“error/{0}”。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Program
{
 private static Random _random = new Random();
 public static void Main()
 {
  new WebHostBuilder()
   .UseKestrel()
   .ConfigureServices(svcs => svcs.AddRouting())
   .Configure(app => app
    .UseStatusCodePagesWithRedirects("~/error/{0}")
    .UseRouter(builder=>builder.MapRoute("error/{statuscode}", HandleError))
   .Run(context=>Task.Run(()=>context.Response.StatusCode = _random.Next(400,599))))
   .Build()
   .Run();
 }
  
 private async static Task HandleError(HttpContext context)
 {
  var statusCode = context.GetRouteData().Values["statuscode"];
  await context.Response.WriteAsync($"Error occurred ({statusCode})");
 }
}

針對該應用的請求總是會得到一個狀態碼在400~599之間的響應, StatusCodePagesMiddleware在此情況下會向我們指定的路徑(“~/error/{statuscode}”)發送一個客戶端重定向。由于重定向請求的路徑與注冊的路由相匹配,所以作為路由處理器的HandleError方法會響應如圖11所示的這個錯誤頁面。

ASP.NET Core應用錯誤處理之StatusCodePagesMiddleware中間件針對響應碼呈現錯誤頁面

UseStatusCodePagesWithReExecute

除了采用客戶端重定向的方式來呈現錯誤頁面之外,我們還可以調用UseStatusCodePagesWithReExecute方法注冊StatusCodePagesMiddleware中間件并讓它采用服務端重定向的方式來處理錯誤請求。如下面的代碼片段所示,當我們調用這個方法的時候不僅可以指定重定向的路徑,還可以指定指定查詢字符串。這里作為重定向地址的參數pathFormat依舊是一個路徑模板,它可以包含一個表示響應狀態的占位符(“{0}”)。

?
1
2
3
4
public static class StatusCodePagesExtensions
{
 public static IApplicationBuilder UseStatusCodePagesWithReExecute(this IApplicationBuilder app, string pathFormat, string queryFormat = null);
}

現在我們對上面演示的這個實例略作修改來演示采服務端重定向呈現出來的錯誤頁面。如下面的代碼片段所示,我們僅僅將針對UseStatusCodePagesWithRedirects方法的調用替換成針對UseStatusCodePagesWithReExecute方法的調用而已。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Program
{
 private static Random _random = new Random();
 public static void Main()
 {
  new WebHostBuilder()
   .UseKestrel()
   .ConfigureServices(svcs => svcs.AddRouting())
   .Configure(app => app
   .UseStatusCodePagesWithReExecute("/error/{0}")
   .UseRouter(builder=>builder.MapRoute("error/{statuscode}", HandleError))
  .Run(context=>Task.Run(()=>context.Response.StatusCode = _random.Next(400,599))))
   .Build()
   .Run();
 }
  
 private async static Task HandleError(HttpContext context)
 {
  var statusCode = context.GetRouteData().Values["statuscode"];
  await context.Response.WriteAsync($"Error occurred ({statusCode})");
 }
}

對于前面演示的實例,由于錯誤頁面是通過客戶端重定向的方式呈現出來的,所以瀏覽器地址欄顯示的是重定向地址。我們在選擇這個實例中采用了服務端重定向,雖然顯示的頁面內容并沒有不同,但是地址欄上的地址是不會發生改變的

ASP.NET Core應用錯誤處理之StatusCodePagesMiddleware中間件針對響應碼呈現錯誤頁面

之所以被命名為UseStatusCodePagesWithReExecute,是因為通過這方法注冊的StatusCodePagesMiddleware中間件進行錯誤處理的時候,它僅僅是提供的重定向路徑和查詢字符串應用到當前HttpContext,然后遞交給后續管道重新執行。UseStatusCodePagesWithReExecute方法中注冊StatusCodePagesMiddleware中間件的實現總體上可以由如下所示的代碼片段來體現。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static class StatusCodePagesExtensions
  public static IApplicationBuilder UseStatusCodePagesWithReExecute(this IApplicationBuilder app,string pathFormat,string queryFormat = null)
  {
    return app.UseStatusCodePages(async context =>
    {
      var newPath = new PathString(string.Format(CultureInfo.InvariantCulture, pathFormat, context.HttpContext.Response.StatusCode));
      var formatedQueryString = queryFormat == null ? null :string.Format(CultureInfo.InvariantCulture, queryFormat, context.HttpContext.Response.StatusCode);     
      context.HttpContext.Request.Path = newPath;
      context.HttpContext.Request.QueryString = newQueryString;
      await context.Next(context.HttpContext);
    });
  }
}

與ExceptionHandlerMiddleware中間價類似,StatusCodePagesMiddleware中間件在處理請求的過程中會改變當前請求上下文的狀態,具體體現在將指定的請求路徑和查詢字符串重新應用到當前請求上下文中。為了不影響前置中間件對請求的正常處理,StatusCodePagesMiddleware中間件在完成自身處理流程之后必須將當前請求上下文恢復到原始的狀態。StatusCodePagesMiddleware中間件依舊是采用一個特性來保存原始的路徑和查詢字符串。這個特性對應的接口為具有如下定義的IStatusCodeReExecuteFeature,令人費解的是該接口僅僅包含兩個針對路徑的屬性,并沒有我們希望的用于攜帶原始查詢上下文的屬性,但是默認實現類型StatusCodeReExecuteFeature包含了這個屬性。

?
1
2
3
4
5
6
7
8
9
10
11
12
public interface IStatusCodeReExecuteFeature
{
  string OriginalPath { get; set; }
  string OriginalPathBase { get; set; }
}
 
public class StatusCodeReExecuteFeature : IStatusCodeReExecuteFeature
{
  public string OriginalPath { get; set; }
  public string OriginalPathBase { get; set; }
  public string OriginalQueryString { get; set; }
}

當StatusCodePagesMiddleware中間件在處理異常請求的過程中,在將指定的重定向路徑和查詢字符串應用到當前請求上下文上之前,它會根據原始的上下文創建一個StatusCodeReExecuteFeature特性對象并將其添加到當前HttpContext之上。當整個請求處理過程結束之后,StatusCodePagesMiddleware中間件還會負責將這個特性從當前HttpContext中移除,并恢復原始的請求路徑和查詢字符串。如下所示的代碼片段體現了UseStatusCodePagesWithReExecute方法的真實邏輯。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public static class StatusCodePagesExtensions
{
  public static IApplicationBuilder UseStatusCodePagesWithReExecute(this IApplicationBuilder app,string pathFormat,string queryFormat = null)
  
    return app.UseStatusCodePages(async context =>
    {
      var newPath = new PathString(string.Format(CultureInfo.InvariantCulture, pathFormat, context.HttpContext.Response.StatusCode));
      var formatedQueryString = queryFormat == null ? null :string.Format(CultureInfo.InvariantCulture, queryFormat, context.HttpContext.Response.StatusCode);
      var newQueryString = queryFormat == null ? QueryString.Empty : new QueryString(formatedQueryString);
 
      var originalPath = context.HttpContext.Request.Path;
      var originalQueryString = context.HttpContext.Request.QueryString;
 
      context.HttpContext.Features.Set<IStatusCodeReExecuteFeature>(new StatusCodeReExecuteFeature()
      {
        OriginalPathBase = context.HttpContext.Request.PathBase.Value,
        OriginalPath = originalPath.Value,
        OriginalQueryString = originalQueryString.HasValue ? originalQueryString.Value : null,
      });
 
      context.HttpContext.Request.Path = newPath;
      context.HttpContext.Request.QueryString = newQueryString;
      try
      {
        await context.Next(context.HttpContext);
      }
      finally
      {
        context.HttpContext.Request.QueryString = originalQueryString;
        context.HttpContext.Request.Path = originalPath;
        context.HttpContext.Features.Set<IStatusCodeReExecuteFeature>(null);
      }
    });
  }
}

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。

原文鏈接:http://www.cnblogs.com/artech/p/error-handling-in-asp-net-core-4.html

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 日本xxxxxxxxx高清hd | 亚洲图片综合网 | 国产欧美亚洲精品第一页青草 | 欧美一区二区三区免费看 | 免费一级黄| 成人特级毛片69免费观看 | 日本不卡在线视频高清免费 | 精品久久久久久国产91 | 猥琐对着美女飞机喷到脸上 | 99久久久久国产精品免费 | 国产精品麻豆免费版 | 动漫美女人物被黄漫在线看 | 欧美日韩精品免费一区二区三区 | 国产一区二区三区在线观看视频 | 国产成人综合手机在线播放 | 精品久久久久久国产 | 亚洲第一成年免费网站 | 日本高清在线看 | 美女的让男人桶爽30分钟的 | 精品福利一区二区免费视频 | 欧美精选视频 | 亚洲欧美久久久久久久久久爽网站 | 国产亚洲人成网站在线观看不卡 | 调教小荡娃h| 国产一区二区三区久久小说 | 欧美成人免费tv在线播放 | 黑帮少爷爱上我第8集最新 荷兰精品女人性hd 和日本免费不卡在线v | 欧美成人中文字幕 | 精东影业传媒全部作品 | 国产拍拍视频一二三四区 | 99久久一香蕉国产线看观看 | 刺激一区仑乱 | 波多野结衣在线看 | 青青草国产免费久久久91 | 幻女free性俄罗斯第一次摘花 | 色综合色综合 | 深夜激情网站 | 欧美久久久久久 | 爽好舒服宝贝添奶吻戏 | 美女扒开两腿露出尿口的视频 | 免费国产一级观看完整版 |