【ASP.NET Core】處理異常--轉

老周寫的【ASP.NET Core】處理異常很是的通俗易懂,拿來記錄下。html

轉自老周:http://www.cnblogs.com/tcjiaan/p/8461408.htmlapi

今天我們聊聊有關異常處理的破事吧,也能夠說是錯誤處理,反正就這個意思,你理解就好,專業名詞沒必要較勁,只有那些吃飽了撐着的「學術人才」纔會跟名詞較勁。瀏覽器

老辦法,我們結合示例來說述,這樣各位觀衆不會乏味。服務器

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

爲了方便人們以腎換 Phone ,老周特地開發了一個在線賣腎系統。大體流程是這樣的,若是你有閒置的腎,能夠打開主頁,輸入你的一些信息,而後報個價,其餘用戶看見後,若是以爲合理,就認購此腎。異步

 

 爲了使操做流程更簡單,易上手,輕入門,該平臺只須要輸入姓名和腎的價格便可參加報價。async

 

大體的頁面代碼以下。ide

        <form method="post">
            <div class="form-group">
                <label for="name">姓名:</label>
                <input type="text" class="form-control" name="name"/>
            </div>
            <div class="form-group">
                <label for="price">價格:</label>
                <input type="number" name="price" class="form-control"/>
            </div>
            <div class="form-group">
                <button type="submit" class="btn btn-success w-100">提  交</button>
            </div>
        </form>

 

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

記住了,與 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 <= 0.0M)
            {
                throw new Exception("靠!你的腎這麼不值錢嗎?還免費送,包郵不?");
            }
            return RedirectToPage("/Success");
        }
    }

 

OnPost 不是 PageModel 基類的方法,而是咱們本身寫的,只是代碼約定,Asp.net Core 裏面用到不少代碼約定,它在運行的時候會查找這些特定的名字。

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

 

通常來講,在 Startup 類的 Configure 方法中,咱們會判斷一下,若是應用程序處於開發階段,爲了方便測試,應該加入這些代碼。

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

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

 

可是,在實際便用時,咱們不能公開這麼詳細的信息,這樣容易勾起人們的犯罪衝動。因此,通常會添加一個頁面,專門用來顯示錯誤信息。好比:

@page

<div class="card">
    <div class="card-header bg-danger">
        <span class="text-light">錯誤</span>
    </div>
    <div class="card-body">
        <span class="card-text">唉,真抱歉。你提交的腎不符合國際標準,沒人要的。</span>
    </div>
</div>

 

而後咱們要在 Startup.Configure 方法中配置一下。

  app.UseExceptionHandler("/Error");

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

 

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

所以,咱們的錯誤頁能夠改一下。

@page
@using Microsoft.AspNetCore.Diagnostics
@{
    IExceptionHandlerFeature exf = HttpContext.Features.Get<IExceptionHandlerFeature>();
    Exception ex = exf?.Error;
}

<div class="card">
    <div class="card-header bg-danger">
        <span class="text-light">錯誤</span>
    </div>
    <div class="card-body">
        @if (ex == null)
        {
            <span class="card-text">唉,真抱歉。你提交的腎不符合國際標準,沒人要的。</span>
        }
        else
        {
            <span class="card-text">@ex.Message</span>
        }
    </div>
</div>

 

經過如下代碼得到異常實例的引用。

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

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

 

 

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

app.UseExceptionHandler(x =>
            {
                x.Run(async context =>
                {
                    var ex = context.Features.Get<Microsoft.AspNetCore.Diagnostics.IExceptionHandlerFeature>()?.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<MyExceptionFilter>();
            });
        }

 

上面代碼添加 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 <= 0)
            {
                throw new ArgumentException("號碼不能小於或等於 0。");
            }
            return Content($"恭喜你,中獎了。\n中獎號碼爲:{val}", "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<string, object>
            {
                ["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 < 0 || n < 0)
            {
                throw new Exception("數值不能小於 0。");
            }
            return Json(new { num1 = m, num2 = n, result = m + n });
        }
    }

 

此處把 attrbute 用到方法上。

 

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

得到正確的結果,如今我們提交小於 0 的參數。就會返回剛剛自定義的錯誤。

 

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

示例源代碼下載地址

相關文章
相關標籤/搜索