天天看點

【5min+】AspNet Core中的全局異常處理

【5min+】AspNet Core中的全局異常處理

系列介紹

【五分鐘的dotnet】是一個利用您的碎片化時間來學習和豐富.net知識的博文系列。它所包含了.net體系中可能會涉及到的方方面面,比如C#的小細節,AspnetCore,微服務中的.net知識等等。

5min+不是超過5分鐘的意思,"+"是知識的增加。so,它是讓您花費5分鐘以下的時間來提升您的知識儲備量。

正文

其實一說到AspNet Core裡面的全局異常,其實大家都不會陌生。因為這玩意兒用的非常頻繁,好的異常處理方案能夠幫助開發者更快速的定位問題,也能夠給使用者更好的使用者體驗。

比如,當您通路到一個網頁,突然,它喵的報錯了!您沒有看錯,它報錯了!!!然後顯示了這樣的一個錯誤頁面:

請問,此刻電腦螢幕前的您會什麼感受。(真想掏出那傳說中的95級史詩巨劍!)

但是,假若我們稍微處理一下這個異常,比如用咱們騰訊爸爸的手段,換個皮膚:

使用者馬上就會想:“哎呀,錯誤就錯誤嘛,孰能無過,程式員鍋鍋也挺辛苦的。”

由此可見!!!全局異常的捕獲和處理是有多麼的重要。

AspNet Core 中的全局處理

IAsyncExceptionFilter

那麼在AspNet Core中我們該如何捕獲和處理異常呢? 可能很多同學都知道:IExceptionFilter 。 這個過濾器應該算是AspNet裡面的老牌過濾器了,從很早就延續至今,它允許咱們捕獲AspNet Core的控制器中的錯誤。不過,對于使用 IExceptionFilter,其實我更建議您考慮它的異步版本: IAsyncExceptionFilter。(别問為什麼,問就是愛的供養)。

那麼我們來看看該過濾器是怎麼使用的呢? 下面以 IAsyncExceptionFilter 為例,對于同步版本其實也是一樣的:

public class MyCustomerExceptionFilter : IAsyncExceptionFilter

{

public Task OnExceptionAsync(ExceptionContext context)
{
    if (context.ExceptionHandled == false)
    {
        string msg = context.Exception.Message;
        context.Result = new ContentResult
        {
            Content = msg,
            StatusCode = StatusCodes.Status200OK,
            ContentType = "text/html;charset=utf-8"
        };
    }
    context.ExceptionHandled = true; //異常已處理了

    return Task.CompletedTask;
}           

}

上面咱們建立了一個自定義的異常過濾器,代碼很簡單,就是報錯了之後依舊讓Http傳回狀态碼為200的結果。并且将錯誤資訊傳回到用戶端。

然後還需要在 Startup.cs 中,告訴 MVC 咱們新加的這個過濾器:

services.AddControllers(options => options.Filters.Add(new MyCustomerExceptionFilter()));

然後就完了,是不是so easy? 來看看結果:

[HttpGet]

public IEnumerable Get()

throw new Exception("has error!");           

如果不增加該過濾器,我們将得到Http狀态碼為500的響應。這對于某些不緻命的意外操作來說,有點殺雞用牛刀的感覺,對于前端使用者來說也不是很友好(明明輸錯了一個字元,就直接被告知網站崩潰,并且出現喬殿下)。

而咱們捕獲了異常,進行特殊處理之後就顯得很友好了。(傳回200,并且告訴使用者輸錯了某字元等)。

在上面的代碼中,您會看到有一行 context.ExceptionHandled = true;。注意!!! 這很關鍵,當您處理完異常之後,請記得将此屬性更改為true,表明異常已經處理過了。如果不更改的話,嘿嘿🤪。會有什麼結果呢? 請看下面↓

中間件處理異常

由于AspNet Core管道的層層傳遞的特點,咱們就有機會在管道中實作全局異常捕獲。 建立一個中間件來試試吧:

public class MyExceptionMiddleware

private readonly RequestDelegate _next;
public MyExceptionMiddleware(RequestDelegate next)
{
    _next = next;
}
public async Task Invoke(HttpContext httpContext)
{
    try
    {
        await _next(httpContext);
    }
    catch (Exception ex)
    {
        httpContext.Response.ContentType = "application/problem+json";

        var title = "An error occured: " + ex.Message;
        var details = ex.ToString();

        var problem = new ProblemDetails
        {
            Status = 200,
            Title = title,
            Detail = details
        };

        //Serialize the problem details object to the Response as JSON (using System.Text.Json)
        var stream = httpContext.Response.Body;
        await JsonSerializer.SerializeAsync(stream, problem);
    }
}           

然後在 Startup.cs 中,注冊管道:

app.UseMiddleware();

來看看效果:

還是原來的味道,還是熟悉的配方,爽歪歪!

管道的添加順序決定了它的執行順序,是以如果您想擴大異常捕獲的範圍,可以将該管道放置在 Configure 的第一行。 但是!! 您會發現,這個預設的AspNet Core項目不是已經在第一行弄了一個異常處理麼? 我*&&……&。

if (env.IsDevelopment())

app.UseDeveloperExceptionPage();           

else

app.UseExceptionHandler("/Error");           

這行代碼大家在初始化新AspNetCore項目時就會看到,也有可能您隻有上半段,這和模闆有關系。不過這都沒有關系,它的作用就是捕獲和處理異常而已。關于 UseDeveloperExceptionPage 該擴充咱們就不多說了,它的意思是:對于開發模式,一旦報錯就會跳轉到錯誤堆棧頁面。 而第二個 UseExceptionHandler 就很有意思了,從它命名就可以看出,它肯定是個錯誤攔截程式。那麼它和咱們自定義的異常處理管道有什麼差別呢?

“不指定肯定有個預設吧!” 是的,它就是預設的錯誤處理。是以,它其實也是一個中間件,它的真身叫做 ExceptionHandlerMiddleware。在使用 UseExceptionHandler 方法時,我們可以選填各種參數。比如上方的代碼,填入了 "/Error" 參數,表示當産生異常的時候,将定向到對應路徑,此處就定位的是: "http://localhost:5001/Error" 。當然您也可以随意指定頁面,比如 漂亮的喬殿下頁面。😝

還有指定 ExceptionHandlerOptions 參數的方法,該參數是ExceptionHandlerMiddleware中間件的重要參數:

參數名 說明

ExceptionHandlingPath 重定向的路徑,比如剛才的 ""/Error"" 實際上就是指定的該參數

ExceptionHandler 錯誤攔截處理程式

ExceptionHandler 允許我們在 ExceptionHandlerMiddleware 内部指定咱們自有的異常處理邏輯。而該參數的類型為 RequestDelegate,是否很眼熟,沒錯,管道處理!是以UseExceptionHandler 提供了一個簡便的寫法,可以讓我們在ExceptionHandlerMiddleware 中又建立自定義的錯誤攔截管道來作為處理程式:

//in Configure()

app.UseExceptionHandler(appbuilder => appbuilder.Use(ExceptionHandlerDemo));

//該内容會在AspNetCore的管道傳回結果至ExceptionHandlerMiddleware時,如果中間件捕獲到了異常時調用

private async Task ExceptionHandlerDemo(HttpContext httpContext, Func next)

//該資訊由ExceptionHandlerMiddleware中間件提供,裡面包含了ExceptionHandlerMiddleware中間件捕獲到的異常資訊。
var exceptionDetails = httpContext.Features.Get<IExceptionHandlerFeature>();
var ex = exceptionDetails?.Error;

if (ex != null)
{
    httpContext.Response.ContentType = "application/problem+json";

    var title = "An error occured: " + ex.Message;
    var details = ex.ToString();

    var problem = new ProblemDetails
    {
        Status = 500,
        Title = title,
        Detail = details
    };

    var stream = httpContext.Response.Body;
    await JsonSerializer.SerializeAsync(stream, problem);
}           

管道 VS 過濾器

那麼上面兩個方法有什麼差別呢? 回答:攔截範圍。

IExceptionFilter 作為MVC中間件之間的内容,它需要MVC在發現錯誤之後将錯誤資訊送出給它處理,是以它的錯誤處理範圍僅限于MVC中間件。是以,假如我們需要捕獲MVC中間件之前的一些錯誤,其實是捕獲不到的。 而對于ExceptionHandlerMiddleware中間件來說就很簡單了,它作為第一個中間件,凡是在它之後的所有錯誤它都能夠捕獲得到。

那麼這麼看來是否IExceptionFilter就毫無用武之地了呢? 非也,假如您想在MVC發生異常時快速捕獲和處理,使用過濾器其實是您不錯得選擇,如果您僅僅關心控制器之間的異常,那麼過濾器也是很好的選擇。

還記得剛開始我們在過濾器中說過的這一行代碼嗎:context.ExceptionHandled = true;。如果在IExceptionFilter中将異常标記為已經處理之後,則第一道異常進行中間件就認為沒有錯誤了,不會進入到處理邏輯中。是以,如果咱們不把該屬性改為 true,很有可能出現攔截結果被覆寫的情況。

原文位址

https://www.cnblogs.com/uoyo/p/12450205.html