當前位置:
首頁 > 最新 > 【ASP.NET Core】處理異常

【ASP.NET Core】處理異常

依照老周的良好作風,開始之前先說點題外話。

前面的博文中,老周介紹過自定義 MVC 視圖的搜索路徑,即向 ViewLocationFormats 列表添加相應的內容,其實,對 Razor Page 模型,也可以向 PageViewLocationFormats 列表添加相應的搜索路徑,比如 /MyPages//.cshtml。其中,0 是視圖名,1 是頁面名稱。比如這樣。

services.AddMvc().AddRazorOptions(opt => { opt.ViewLocationFormats ... opt.PageViewLocationFormats ... });

然而,我們知道,基於 Razor 的 Web Page 模型是以頁面為單位的,也就是說路徑路由是直接指向頁面的(不包含.cshtml 擴展名),即不需要 MVC 模型的路由方式。所以,我們並不需要修改 PageViewLocationFormats 中的內容。許多時候,我們只要告訴應用程序在哪個目錄下查找 Page 就行了。

默認的搜索位置是 /Pages 目錄,我們可以通過以下代碼來修改。

public void ConfigureServices(IServiceCollection services)

{

services.AddMvc().AddRazorPagesOptions(opt =>

{

opt.RootDirectory = "/UI";

});

}

以上代碼寫在 Startup 類中,這個應該明白吧。RootDirectory 就是用來指定應用程序查找 Razor 頁面的根目錄路徑,此處我指寫了 /UI,所以,在我的項目中,我只要建一個 UI 目錄,然後各類 Razor 頁就往裡面放就行了。

好了,題外話扯完了,開始說正題吧。今天咱們聊聊有關異常處理的破事吧,也可以說是錯誤處理,反正就這個意思,你理解就好,專業名詞不必較勁,只有那些吃飽了撐著的「學術人才」才會跟名詞較勁。

老辦法,咱們結合示例來講述,這樣各位觀眾不會乏味。

大家知道,娛樂產品腎Phone已經成為流行玩具,近年來,購買腎Phone不一定只能用貨幣,比較典型的一種支付方式是賣腎買Phone。說實話,現在許多國產娛樂產品也很便宜,配置也不錯,幾百塊錢就能玩得刷刷響了,割腎真沒什麼必要。

為了方便人們以腎換 Phone ,老周特意開發了一個在線賣腎系統。大致流程是這樣的,如果你有閑置的腎,可以打開主頁,輸入你的一些信息,然後報個價,其他用戶看見後,如果覺得合理,就認購此腎。

為了使操作流程更簡單,易上手,輕入門,該平台只需要輸入姓名和腎的價格即可參加報價。

大致的頁面代碼如下。

姓名:

價格:

提 交

Razor 頁面很像我們以前玩過的 aspx 頁面,每個頁面都配套一個隱藏代碼文件。Razor 頁也會配有一個頁面模型類,注意這個模型類要從 PageModel 派生,不是 Page 類,別搞錯了,Page 類只是作為生成 HTML 代碼的基類,我們的 .cshtml 文件在預編譯後,是隱式繼承自 RazorPage 類的。除非你要開發自己的標記語言,否則你不必理會這些類。

記住了,與 Razor 頁關聯的模型類是從 PageModel 類派生的,比如,本例中,當有人填寫了閑置腎的相關信息後,以 POST 方式提交,這是候,如果頁面模型類中包含了名字為 OnPost、OnPostAsync ……的方法時,就會自動調用。如果想把我們上面那個 form 中的 name 和 price 的值傳遞給方法,直接讓 OnPost 方法的參數與 form 中的元素名稱相同就可以了。

public class IndexModel : PageModel

{

public IActionResult OnPost(string name, decimal price)

{

if (string.IsNullOrWhiteSpace(name))

{

throw new Exception("你怎麼不留下姓名啊,賣腎又不是丟人的事。");

}

if(price

{

throw new Exception("靠!你的腎這麼不值錢嗎?還免費送,包郵不?");

}

return RedirectToPage("/Success");

}

}

OnPost 不是 PageModel 基類的方法,而是我們自己寫的,只是代碼約定,Asp.net Core 裡面用到很多代碼約定,它在運行的時候會查找這些特定的名字。

上面代碼中,還對傳遞進來的 form 值進行驗證,如果不符合要求,會拋出異常。

一般來說,在 Startup 類的 Configure 方法中,我們會判斷一下,如果應用程序處於開發階段,為了方便測試,應該加入這些代碼。

if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); }

這樣,我們在測試時能看到詳細的異常信息。

但是,在實際便用時,我們不能公開這麼詳細的信息,這樣容易勾起人們的犯罪衝動。所以,一般會添加一個頁面,專門用來顯示錯誤信息。比如:

@page

錯誤

唉,真抱歉。你提交的腎不符合國際標準,沒人要的。

然後我們要在 Startup.Configure 方法中配置一下。

app.UseExceptionHandler("/Error");

加上這一行後,當發生異常時,就會跳轉到 /Error 頁面。

不過,你也許會覺得,雖然不能公開異常信息,但一些必要的描述應該要的,不然,用戶不知道發生了啥事。我們可以通過 HttpContext 的 Features 集合獲取一個用來處理異常的 Feature,它的原型介面是 IExceptionHandlerFeature,我們不必關心它的實現類型是誰,只要訪問它的 Error 屬性就能得到關聯的 Exception 實例。

因此,我們的錯誤頁可以改一下。

@page

@using Microsoft.AspNetCore.Diagnostics

@{

IExceptionHandlerFeature exf = HttpContext.Features.Get();

Exception ex = exf?.Error;

}

錯誤

@if (ex == null)

{

唉,真抱歉。你提交的腎不符合國際標準,沒人要的。

}

else

{

@ex.Message

}

通過以下代碼獲得異常實例的引用。

IExceptionHandlerFeature exf = HttpContext.Features.Get(); Exception ex = exf?.Error;

這樣就可以在頁面上顯示異常的描述信息了。

可能你又想到了,我不想輸出個頁面,我只想返回一些簡單的文本,那麼,你在 Startup.Configure 中可以這樣寫。

app.UseExceptionHandler(x =>

{

x.Run(async context =>

{

var ex = context.Features.Get()?.Error;

string msg = ex == null ? "發生錯誤。" : ex.Message;

context.Response.ContentType = "text/plain;charset=utf-8";

await context.Response.WriteAsync(msg);

});

});

裡面的變數 x 就是當前的 IApplicationBuilder ,與傳遞給 Configure 方法的 app 參數類型一樣,這時候我們可以用 Reponse 的方法返回自定義的文本。

好了,今天的內容就介紹到這兒吧,其實異常處理還有一種方法——使用 Filter,這個咱們留到下一篇博文再和大夥分享。

上一篇中,老周給大夥伴們扯了有關 ASP.NET Core 中異常處理的簡單方法。按照老周的優良作風,我們應該順著這個思路繼續挖掘。

本文老周就不自量力地介紹一下如何使用 MVC Filter 來處理異常。MVC 模型(當然適用於 Razor Page 、Web API 模型)可以用一系列的 Filter 來對請求與回應消息進行過濾處理。其中,在 Microsoft.AspNetCore.Mvc.Filters 命名空間下,你會發現有兩個介面,它們跟異常處理有關:

IExceptionFilter:實現 OnException 方法,可以自定義回傳給客戶端的異常信息。

IAsyncExceptionFilter:跟上面的一樣的,只不過這廝支持非同步等待而已。

在實現處理異常的 Filter 時,傳給 OnException / OnExceptionAsync 方法的有一個 ExceptionContext 類型參數,我們可以通過它來設置自定義的返回結果。

訪問 Exception 屬性,你可以得到相關的異常實例,當然這個屬性是可寫的,所以你可以獲取異常實例後,將它改為其他異常實例,再重新賦給這個屬性,比如,你用你自己編寫的異常類來重新封裝。通過 Result 屬性設置返回結果,這個與 MVC Action 方法的返回方法一樣,不同的是,在 Action 方法中,你可以調用 Controller 基類的方法來返回對應的 Result ,而對於 Result 屬性,你必須顯式地去創建實現了 IActionResult 介面的類型實例。

另外,值得注意的是,ExceptionContext 類還有一個 ExceptionHandled 屬性,該屬性值可讀可寫,主要是用於標識當前發生的異常是否已經過處理。這主要是應對 Filter 的執行順序的,一種情況是你可能使用了多個 Filter 來處理異常,在處理過程中你就可以將這個屬性值設為 true 以表示這個錯誤已處理過了,後面的就不必處理了;另一種情況是,以 Attribute 方式使用的 Filter 的優先順序會比全局使用的 Filter 高,也許在 Attribute 上我沒有對異常進行處理,那麼到了全局 Filter 執行的時候,我就可以檢查一下這個屬性,如果沒有處理就進行一下處理。關於 Attribute 方式使用 Filter 老周隨後會說的,這裡先提一下。

好了,咱們先說說如何實現自己的異常處理 Filter,其實很簡單,看下面代碼。

public class MyExceptionFilter : IExceptionFilter, IFilterMetadata

{

public void OnException(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; //異常已處理了

}

在 OnException 方法中,我直接獲取異常信息,然後用一個 ContentResult 對象來返回,這個是類似於 MVC 中 Controller . Action 方法返回結果,我這裡簡單地以 HTML 文本形式返回,一旦處理到異常,應用程序會自動把這個 Result 返回給客戶端。

你可能發現了,我除了實現 IExceptionFilter 介面外,還實現了一個 IFilterMetadata 介面,這個介面是必須的,不然待會兒我們無法應用這個 Filter 了,為什麼呢,等一下你就會明白了。

這裡實現的這個是同步調用的,如果你希望有一個可非同步等待的版本,那麼,你就順便實現一下 IAsyncExceptionFilter 介面。把上面的代碼改為:

public class MyExceptionFilter : IExceptionFilter, IAsyncExceptionFilter, IFilterMetadata

{

public void OnException(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; //異常已處理了

}

public Task OnExceptionAsync(ExceptionContext context)

{

OnException(context);

return Task.CompletedTask;

}

}

好了,接下來咱們得考慮怎麼用它了。在 Startup.ConfigureServices 方法中,添加 MVC 功能後可以把咱們自己寫的 Filter 添加進去。

public void ConfigureServices(IServiceCollection services)

{

services.AddMvc(opt =>

{

opt.Filters.Add();

});

}

上面代碼添加 Filter 後,是用於全局的,說白了,當應用程序內不管哪個 Controller 裡面發生的異常,都會經過咱們添加的 Filter 處理。

現在我們測試一下這個異常處理的 Filter 起到什麼作用。為了不影響測試,請把 Configure 方法中這段代碼刪除。

public void Configure(IApplicationBuilder app, IHostingEnvironment env)

{

if (env.IsDevelopment())

{

app.UseDeveloperExceptionPage();

}

app.UseMvc();

}

變成這樣

public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); }

然後,隨便弄段代碼來測試。

[HttpPost("/code")]

public IActionResult SubmitSome(int val)

{

if(val

{

throw new ArgumentException("號碼不能小於或等於 0。");

}

return Content($"恭喜你,中獎了。
中獎號碼為:", "text/html;charset=utf-8");

}

這個邏輯很簡單,就是在前台頁面輸入一個數值,然後 POST 上來,如果數值不是大於 0 的值就拋異常。

然後我故意輸入一個 -10。

POST 後在伺服器上引發異常。

繼續執行,讓 Filter 對異常進行處理。

最後,異常信息就返回給瀏覽器了。

這樣說明咱們寫的 Filter 起作用了。

剛剛說過,在 ConfigureServices 方法中添加的 Filter 是用於全局的,如果我們的項目中有個別的 Controller 或者 Controller 中的個別方法,希望使用專門的 Filter 去處理異常,這時候就可以考慮以 Attribute 的方式去處理。

要用 Attribute 方式處理異常,需要實現 ExceptionFilterAttribute 抽象類。該抽象類已實現了咱們上面提到過的幾個介面。

這個類還實現了 IOrderedFilter 介面,可以用來安排多個 Attribute 實例在處理異常上的順序(假設你用了多個實例來處理)。

下面咱們自己實現一個 Attribute ,用來處理異常。

public class MyExceptionFilterAttribute : ExceptionFilterAttribute

{

public override void OnException(ExceptionContext context)

{

var ex = context.Exception;

// 構建錯誤信息對象

var dic = new Dictionary

{

["err_code"] = 80250,

["err_msg"] = ex.Message,

["err_sol"] = "建議攜帶你的數據到醫院做檢查。"

};

// 設置結果

context.Result = new JsonResult(dic);

context.ExceptionHandled = true;

}

public override Task OnExceptionAsync(ExceptionContext context)

{

OnException(context);

return Task.CompletedTask;

}

}

上面代碼中,我以 JSON 格式返回錯誤數據。

這個 Attribute 可以用於類與方法,然後咱們用 Web API 來測試。

[Route("api/[controller]")]

public class DemoController : Controller

{

[HttpGet]

[MyExceptionFilter]

public IActionResult Compute(int m, int n)

{

if (m

{

throw new Exception("數值不能小於 0。");

}

return Json(new { num1 = m, num2 = n, result = m + n });

}

}

此處把 attrbute 用到方法上。

運行應用程序,然後請出 Postman 大叔來幫我們測試 Web API。為參數 m 和 n 賦值,然後以 GET 方式發送請求。

獲得正確的結果,現在咱們提交小於 0 的參數。就會返回剛剛自定義的錯誤。

好了,今天的內容就說到這裡,下次有空繼續扯。

原文地址:http://www.cnblogs.com/tcjiaan/p/8461408.html


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 dotNET跨平台 的精彩文章:

Blazor正式成為Microsoft官方.NET 和WebAssembly項目

TAG:dotNET跨平台 |