溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

ASP.NET Core中StatusCodePagesMiddleware中間件針對響應(yīng)碼呈現(xiàn)錯誤頁面怎么辦

發(fā)布時間:2021-07-12 14:24:50 來源:億速云 閱讀:172 作者:小新 欄目:開發(fā)技術(shù)

小編給大家分享一下ASP.NET Core中StatusCodePagesMiddleware中間件針對響應(yīng)碼呈現(xiàn)錯誤頁面怎么辦,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

前言

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

 public class StatusCodePagesMiddleware
 {
 public StatusCodePagesMiddleware(RequestDelegate next, IOptions<StatusCodePagesOptions> options);
 public Task Invoke(HttpContext context);
 }

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

 public class StatusCodePagesOptions
 {
 public Func<StatusCodeContext, Task> HandleAsync { get; set; }
 }

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

 public class StatusCodeContext
 { 
 public HttpContext  HttpContext { get; }
 public RequestDelegate  Next { get; }
 public StatusCodePagesOptions Options { get; }
 
 public StatusCodeContext(HttpContext context, StatusCodePagesOptions options, RequestDelegate next);
 }

一、針對響應(yīng)狀態(tài)碼的錯誤處理

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

 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>對象,然后創(chuàng)建一個StatusCodeContext對象作為輸入?yún)?shù)調(diào)用這個委托對象即可。

二、阻止異常處理

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

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

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

 public interface IStatusCodePagesFeature
 {
  bool Enabled { get; set; }
 }
 
 public class StatusCodePagesFeature : IStatusCodePagesFeature
 {
  public bool Enabled { get; set; } = true ;
 }

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

 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中間件。在下面這個應(yīng)用中,我們將針對請求的處理定義在Invoke方法中,該方法會返回一個狀態(tài)碼為“401 Unauthorized”的響應(yīng)。我們通過隨機(jī)數(shù)讓這個方法會在50%的情況下利用StatusCodePagesFeature特性來阻止StatusCodePagesMiddleware中間件自身對錯誤的處理。我們通過調(diào)用擴(kuò)展方法UseStatusCodePages注冊的StatusCodePagesMiddleware中間件會直接響應(yīng)一個內(nèi)容為“Error occurred!”的字符串。

 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;
  }
 }

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

 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中間件

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

UseStatusCodePages

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

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

 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

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

 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;
    });
   }
  }
 }

我們通過一個簡單的應(yīng)用來演示針對客戶端重定向的錯誤頁面呈現(xiàn)方式。我們在如下這個應(yīng)用中注冊了一個路由模板為“error/{statuscode}”的路由,路由參數(shù)“statuscode”自然代表響應(yīng)的狀態(tài)碼。在作為路由處理器的HandleError方法中,我們會直接響應(yīng)一個包含響應(yīng)狀態(tài)碼的字符串。我們調(diào)用UseStatusCodePagesWithRedirects方法注冊StatusCodePagesMiddleware中間件的時候?qū)⒅囟x路徑設(shè)置為“error/{0}”。

 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})");
  }
 }

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

ASP.NET Core中StatusCodePagesMiddleware中間件針對響應(yīng)碼呈現(xiàn)錯誤頁面怎么辦

UseStatusCodePagesWithReExecute

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

 public static class StatusCodePagesExtensions
 {
  public static IApplicationBuilder UseStatusCodePagesWithReExecute(this IApplicationBuilder app, string pathFormat, string queryFormat = null);
 }

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

 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})");
  }
 }

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

ASP.NET Core中StatusCodePagesMiddleware中間件針對響應(yīng)碼呈現(xiàn)錯誤頁面怎么辦

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

 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中間件在處理請求的過程中會改變當(dāng)前請求上下文的狀態(tài),具體體現(xiàn)在將指定的請求路徑和查詢字符串重新應(yīng)用到當(dāng)前請求上下文中。為了不影響前置中間件對請求的正常處理,StatusCodePagesMiddleware中間件在完成自身處理流程之后必須將當(dāng)前請求上下文恢復(fù)到原始的狀態(tài)。StatusCodePagesMiddleware中間件依舊是采用一個特性來保存原始的路徑和查詢字符串。這個特性對應(yīng)的接口為具有如下定義的IStatusCodeReExecuteFeature,令人費解的是該接口僅僅包含兩個針對路徑的屬性,并沒有我們希望的用于攜帶原始查詢上下文的屬性,但是默認(rèn)實現(xiàn)類型StatusCodeReExecuteFeature包含了這個屬性。

 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; }
 }

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

 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);
       }
     });
   }
 }

以上是“ASP.NET Core中StatusCodePagesMiddleware中間件針對響應(yīng)碼呈現(xiàn)錯誤頁面怎么辦”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI