【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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章