C#進階系列——WebApi 異常處理解決方案

前言:上篇C#進階系列——WebApi接口傳參再也不困惑:傳參詳解介紹了WebApi參數的傳遞,這篇來看看WebApi裏面異常的處理。關於異常處理,做爲程序員的咱們確定不陌生,記得在介紹 AOP 的時候,咱們講過經過AOP能夠統一截獲異常。那麼在咱們的WebApi裏面通常是怎麼處理異常的呢,今天這一篇,博主帶着你們一塊兒來實踐下WebApi的異常處理。html

WebApi系列文章程序員

爲何說是實踐?由於在http://www.asp.net裏面已經明確給出WebApi的異常處理機制。光有理論還不夠,今天咱們仍是來試一把。經過實踐,咱們可能發現一些更詳盡的用法。api

1、使用異常篩選器捕獲全部異常

咱們知道,通常狀況下,WebApi做爲服務使用,每次客戶端發送http請求到咱們的WebApi服務裏面,服務端獲得結果輸出response到客戶端。這個過程當中,一旦服務端發生異常,會統一貫客戶端返回500的錯誤。跨域

        [HttpGet]
        public string GetAllChargingData([FromUri]TB_CHARGING obj)
        {
            throw new NotImplementedException("方法不被支持");
        }

咱們來看看http請求瀏覽器

而有些時候,咱們客戶端須要獲得更加精確的錯誤碼來判斷異常類型,怎麼辦呢?緩存

記得在介紹AOP的時候,咱們介紹過MVC裏面的IExceptionFilter接口,這個接口用於定義異常篩選器所需的方法,在WebApi裏面,也有這麼一個異常篩選器,下面咱們經過一個實例來看看具體如何實現。服務器

首先在App_Start裏面新建一個類WebApiExceptionFilterAttribute.cs,繼承ExceptionFilterAttribute,重寫OnException方法asp.net

    public class WebApiExceptionFilterAttribute : ExceptionFilterAttribute 
    {
        //重寫基類的異常處理方法
        public override void OnException(HttpActionExecutedContext actionExecutedContext)
        {
            //1.異常日誌記錄(正式項目裏面通常是用log4net記錄異常日誌)
            Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "——" +
                actionExecutedContext.Exception.GetType().ToString() + "" + actionExecutedContext.Exception.Message + "——堆棧信息:" +
                actionExecutedContext.Exception.StackTrace);

            //2.返回調用方具體的異常信息
            if (actionExecutedContext.Exception is NotImplementedException)
            {
                actionExecutedContext.Response = new HttpResponseMessage(HttpStatusCode.NotImplemented);
            }
            else if (actionExecutedContext.Exception is TimeoutException)
            {
                actionExecutedContext.Response = new HttpResponseMessage(HttpStatusCode.RequestTimeout);
            }
            //.....這裏能夠根據項目須要返回到客戶端特定的狀態碼。若是找不到相應的異常,統一返回服務端錯誤500
            else
            {
                actionExecutedContext.Response = new HttpResponseMessage(HttpStatusCode.InternalServerError);
            }

            base.OnException(actionExecutedContext);
        }
    }

 代碼解析:經過判斷異常的具體類型,向客戶端返回不一樣的http狀態碼,示例裏面寫了兩個,能夠根據項目的實際狀況加一些特定的咱們想要捕獲的異常,而後將對應的狀態碼寫入http請求的response裏面,對於一些咱們沒法判斷類型的異常,統一返回服務端錯誤500。關於http的狀態碼,framework裏面定義了一些常見的類型,咱們大概看看:ide

#region 程序集 System.dll, v4.0.0.0
// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\System.dll
#endregion

using System;

namespace System.Net
{
    // 摘要: 
    //     包含爲 HTTP 定義的狀態代碼的值。
    public enum HttpStatusCode
    {
        // 摘要: 
        //     等效於 HTTP 狀態 100。 System.Net.HttpStatusCode.Continue 指示客戶端可能繼續其請求。
        Continue = 100,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 101。 System.Net.HttpStatusCode.SwitchingProtocols 指示正在更改協議版本或協議。
        SwitchingProtocols = 101,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 200。 System.Net.HttpStatusCode.OK 指示請求成功,且請求的信息包含在響應中。 這是最常接收的狀態代碼。
        OK = 200,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 201。 System.Net.HttpStatusCode.Created 指示請求致使在響應被髮送前建立新資源。
        Created = 201,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 202。 System.Net.HttpStatusCode.Accepted 指示請求已被接受作進一步處理。
        Accepted = 202,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 203。 System.Net.HttpStatusCode.NonAuthoritativeInformation 指示返回的元信息來自緩存副本而不是原始服務器,所以可能不正確。
        NonAuthoritativeInformation = 203,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 204。 System.Net.HttpStatusCode.NoContent 指示已成功處理請求而且響應已被設定爲無內容。
        NoContent = 204,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 205。 System.Net.HttpStatusCode.ResetContent 指示客戶端應重置(或從新加載)當前資源。
        ResetContent = 205,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 206。 System.Net.HttpStatusCode.PartialContent 指示響應是包括字節範圍的 GET
        //     請求所請求的部分響應。
        PartialContent = 206,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 300。 System.Net.HttpStatusCode.MultipleChoices 指示請求的信息有多種表示形式。
        //     默認操做是將此狀態視爲重定向,並遵循與此響應關聯的 Location 標頭的內容。
        MultipleChoices = 300,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 300。 System.Net.HttpStatusCode.Ambiguous 指示請求的信息有多種表示形式。 默認操做是將此狀態視爲重定向,並遵循與此響應關聯的
        //     Location 標頭的內容。
        Ambiguous = 300,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 301。 System.Net.HttpStatusCode.MovedPermanently 指示請求的信息已移到 Location
        //     頭中指定的 URI 處。 接收到此狀態時的默認操做爲遵循與響應關聯的 Location 頭。
        MovedPermanently = 301,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 301。 System.Net.HttpStatusCode.Moved 指示請求的信息已移到 Location 頭中指定的
        //     URI 處。 接收到此狀態時的默認操做爲遵循與響應關聯的 Location 頭。 原始請求方法爲 POST 時,重定向的請求將使用 GET 方法。
        Moved = 301,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 302。 System.Net.HttpStatusCode.Found 指示請求的信息位於 Location 頭中指定的
        //     URI 處。 接收到此狀態時的默認操做爲遵循與響應關聯的 Location 頭。 原始請求方法爲 POST 時,重定向的請求將使用 GET 方法。
        Found = 302,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 302。 System.Net.HttpStatusCode.Redirect 指示請求的信息位於 Location 頭中指定的
        //     URI 處。 接收到此狀態時的默認操做爲遵循與響應關聯的 Location 頭。 原始請求方法爲 POST 時,重定向的請求將使用 GET 方法。
        Redirect = 302,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 303。 做爲 POST 的結果,System.Net.HttpStatusCode.SeeOther 將客戶端自動重定向到
        //     Location 頭中指定的 URI。 用 GET 生成對 Location 標頭所指定的資源的請求。
        SeeOther = 303,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 303。 做爲 POST 的結果,System.Net.HttpStatusCode.RedirectMethod 將客戶端自動重定向到
        //     Location 頭中指定的 URI。 用 GET 生成對 Location 標頭所指定的資源的請求。
        RedirectMethod = 303,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 304。 System.Net.HttpStatusCode.NotModified 指示客戶端的緩存副本是最新的。 未傳輸此資源的內容。
        NotModified = 304,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 305。 System.Net.HttpStatusCode.UseProxy 指示請求應使用位於 Location 頭中指定的
        //     URI 的代理服務器。
        UseProxy = 305,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 306。 System.Net.HttpStatusCode.Unused 是未徹底指定的 HTTP/1.1 規範的建議擴展。
        Unused = 306,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 307。 System.Net.HttpStatusCode.RedirectKeepVerb 指示請求信息位於 Location
        //     頭中指定的 URI 處。 接收到此狀態時的默認操做爲遵循與響應關聯的 Location 頭。 原始請求方法爲 POST 時,重定向的請求還將使用
        //     POST 方法。
        RedirectKeepVerb = 307,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 307。 System.Net.HttpStatusCode.TemporaryRedirect 指示請求信息位於 Location
        //     頭中指定的 URI 處。 接收到此狀態時的默認操做爲遵循與響應關聯的 Location 頭。 原始請求方法爲 POST 時,重定向的請求還將使用
        //     POST 方法。
        TemporaryRedirect = 307,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 400。 System.Net.HttpStatusCode.BadRequest 指示服務器未能識別請求。 若是沒有其餘適用的錯誤,或者不知道準確的錯誤或錯誤沒有本身的錯誤代碼,則發送
        //     System.Net.HttpStatusCode.BadRequest。
        BadRequest = 400,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 401。 System.Net.HttpStatusCode.Unauthorized 指示請求的資源要求身份驗證。 WWW-Authenticate
        //     頭包含如何執行身份驗證的詳細信息。
        Unauthorized = 401,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 402。 保留 System.Net.HttpStatusCode.PaymentRequired 以供未來使用。
        PaymentRequired = 402,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 403。 System.Net.HttpStatusCode.Forbidden 指示服務器拒絕知足請求。
        Forbidden = 403,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 404。 System.Net.HttpStatusCode.NotFound 指示請求的資源不在服務器上。
        NotFound = 404,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 405。 System.Net.HttpStatusCode.MethodNotAllowed 指示請求的資源上不容許請求方法(POST
        //     或 GET)。
        MethodNotAllowed = 405,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 406。 System.Net.HttpStatusCode.NotAcceptable 指示客戶端已用 Accept 頭指示將不接受資源的任何可用表示形式。
        NotAcceptable = 406,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 407。 System.Net.HttpStatusCode.ProxyAuthenticationRequired 指示請求的代理要求身份驗證。
        //     Proxy-authenticate 頭包含如何執行身份驗證的詳細信息。
        ProxyAuthenticationRequired = 407,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 408。 System.Net.HttpStatusCode.RequestTimeout 指示客戶端沒有在服務器指望請求的時間內發送請求。
        RequestTimeout = 408,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 409。 System.Net.HttpStatusCode.Conflict 指示因爲服務器上的衝突而未能執行請求。
        Conflict = 409,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 410。 System.Net.HttpStatusCode.Gone 指示請求的資源再也不可用。
        Gone = 410,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 411。 System.Net.HttpStatusCode.LengthRequired 指示缺乏必需的 Content-length
        //     頭。
        LengthRequired = 411,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 412。 System.Net.HttpStatusCode.PreconditionFailed 指示爲此請求設置的條件失敗,且沒法執行此請求。
        //     條件是用條件請求標頭(如 If-Match、If-None-Match 或 If-Unmodified-Since)設置的。
        PreconditionFailed = 412,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 413。 System.Net.HttpStatusCode.RequestEntityTooLarge 指示請求太大,服務器沒法處理。
        RequestEntityTooLarge = 413,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 414。 System.Net.HttpStatusCode.RequestUriTooLong 指示 URI 太長。
        RequestUriTooLong = 414,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 415。 System.Net.HttpStatusCode.UnsupportedMediaType 指示請求是不支持的類型。
        UnsupportedMediaType = 415,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 416。 System.Net.HttpStatusCode.RequestedRangeNotSatisfiable 指示沒法返回從資源請求的數據範圍,由於範圍的開頭在資源的開頭以前,或由於範圍的結尾在資源的結尾以後。
        RequestedRangeNotSatisfiable = 416,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 417。 System.Net.HttpStatusCode.ExpectationFailed 指示服務器未能符合 Expect
        //     頭中給定的預期值。
        ExpectationFailed = 417,
        //
        UpgradeRequired = 426,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 500。 System.Net.HttpStatusCode.InternalServerError 指示服務器上發生了通常錯誤。
        InternalServerError = 500,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 501。 System.Net.HttpStatusCode.NotImplemented 指示服務器不支持請求的函數。
        NotImplemented = 501,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 502。 System.Net.HttpStatusCode.BadGateway 指示中間代理服務器從另外一代理或原始服務器接收到錯誤響應。
        BadGateway = 502,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 503。 System.Net.HttpStatusCode.ServiceUnavailable 指示服務器暫時不可用,一般是因爲過多加載或維護。
        ServiceUnavailable = 503,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 504。 System.Net.HttpStatusCode.GatewayTimeout 指示中間代理服務器在等待來自另外一個代理或原始服務器的響應時已超時。
        GatewayTimeout = 504,
        //
        // 摘要: 
        //     等效於 HTTP 狀態 505。 System.Net.HttpStatusCode.HttpVersionNotSupported 指示服務器不支持請求的
        //     HTTP 版本。
        HttpVersionNotSupported = 505,
    }
}
Http狀態碼

定義好了異常處理方法,剩下的就是如何使用了。能夠根據實際狀況,在不一樣級別使用統一的異常處理機制。函數

一、接口級別

        [WebApiExceptionFilter]
        [HttpGet]
        public string GetAllChargingData([FromUri]TB_CHARGING obj)
        {
            throw new NotImplementedException("方法不被支持");
        }

執行到異常後,會先進到OnException方法:

執行完成以後瀏覽器查看:

若是須要,甚至能夠向Status Code裏面寫入自定義的描述信息,而且還能夠向咱們的Response的Content裏面寫入咱們想要的信息。咱們稍微改下OnException方法:

       if (actionExecutedContext.Exception is NotImplementedException)
            {
                var oResponse = new HttpResponseMessage(HttpStatusCode.NotImplemented);
                oResponse.Content = new StringContent("方法不被支持");
                oResponse.ReasonPhrase = "This Func is Not Supported";
                actionExecutedContext.Response = oResponse;
            }

看看ReasonPhrase描述信息

看看Response的描述信息

二、控制器級別

若是想要某一個或者多個控制器裏面的全部接口都使用異常過濾,直接在控制器上面標註特性便可。

  • 某一個控制器上面啓用異常過濾
  [WebApiExceptionFilter]
    public class ChargingController : BaseApiController
    {
        #region Get
        [HttpGet]
        public string GetAllChargingData([FromUri]TB_CHARGING obj)
        {
            throw new NotImplementedException("方法不被支持");
        }
    }
  • 多個控制器上面同時啓用異常過濾
    [WebApiExceptionFilter]
    public class BaseApiController : ApiController
    {
    }
    public class ChargingController : BaseApiController
    {
        #region Get
        [HttpGet]
        public string GetAllChargingData([FromUri]TB_CHARGING obj)
        {
            throw new NotImplementedException("方法不被支持");
        }
    }

這樣,全部繼承BaseApiController的子類都會啓用異常過濾。

三、全局配置

若是須要對整個應用程序都啓用異常過濾,則須要作以下兩步:

  一、在Global.asax全局配置裏面添加 GlobalConfiguration.Configuration.Filters.Add(new WebApiExceptionFilterAttribute()); 這一句,以下:

void Application_Start(object sender, EventArgs e)
        {
            // 在應用程序啓動時運行的代碼
            AreaRegistration.RegisterAllAreas();
            GlobalConfiguration.Configure(WebApiConfig.Register);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            GlobalConfiguration.Configuration.Filters.Add(new WebApiExceptionFilterAttribute());
        }

  二、在WebApiConfig.cs文件的Register方法裏面添加  config.Filters.Add(new WebApiExceptionFilterAttribute()); 這一句,以下:

        public static void Register(HttpConfiguration config)
        {
            //跨域配置
            config.EnableCors(new EnableCorsAttribute("*", "*", "*"));

            // Web API 路由
            config.MapHttpAttributeRoutes();

            RouteTable.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{action}/{id}",
                defaults: new { id = RouteParameter.Optional }
            ).RouteHandler = new SessionControllerRouteHandler();

            config.Filters.Add(new WebApiExceptionFilterAttribute());
        }

2、HttpResponseException自定義異常信息

上面說的是全局的異常捕獲以及處理方式,在某些狀況下,咱們但願以異常的方式向客戶端發送相關信息,可能就須要用到咱們的HttpResponseException。好比:

        [HttpGet]
        public TB_CHARGING GetById(string id)
        {
            //從後臺查詢實體
            var oModel = server.Find(id);
            if (oModel == null)
            {
                var resp = new HttpResponseMessage(HttpStatusCode.NotFound)
                {
                    Content = new StringContent(string.Format("沒有找到id={0}的對象", id)),
                    ReasonPhrase = "object is not found"
                };
                throw new HttpResponseException(resp);
            }
            return oModel;
        }

執行以後瀏覽器裏面查看結果:

代碼釋疑:細心的朋友可能,發現了,這裏既使用了HttpResponseMessage,又使用了HttpResponseException,那麼,像這種可控的異常,咱們是否能夠直接以HttpResponseMessage的形式直接返回到客戶端而不用拋出異常呢?這裏就要談談這兩個對象的區別了,博主的理解是HttpResonseMessage對象用來響應訊息幷包含狀態碼及數據內容,HttpResponseException對象用來向客戶端返回包含錯誤訊息的異常。

在網上看到一篇 文章 這樣描述二者的區別:當呼叫 Web API 服務時發生了與預期上不一樣的錯誤時,理當應該停止程序返回錯誤訊息,這時對於錯誤的返回就該使用 HttpResponseException,而使用 HttpResponseMessage 則是表明着當客戶端發送了一個工做請求而 Web API 正確的完成了這個工做,就可以使用 HttpResponseMessage 返回一個 201 的訊息,因此 HttpResponseMessage 與 HttpResponseException 在使用上根本的目標就是不一樣的,用 HttpResponseMessage 去返回一個例外錯誤也會讓程序結構難以辨別且不夠清晰。

3、返回HttpError

HttpError對象提供一致的方法來響應正文中返回錯誤的信息。準確來講,HttpError並非一個異常,只是用來包裝錯誤信息的一個對象。其實在某必定的程度上,HttpError和HttpResponseMessage使用比較類似,兩者均可以向客戶端返回http狀態碼和錯誤訊息,而且均可以包含在HttpResponseException對象中發回到客戶端。可是,通常狀況下,HttpError只有在向客戶端返回錯誤訊息的時候纔會使用,而HttpResponseMessage對象既能夠返回錯誤訊息,也可返回請求正確的消息。其實關於HttpError沒什麼特別好講的,咱們來看一個例子就能明白:

    public HttpResponseMessage Update(dynamic obj)
        {
            TB_Product oModel = null;
            try
            {
                var id = Convert.ToString(obj.id);
                oModel = Newtonsoft.Json.JsonConvert.DeserializeObject<TB_Product>(Convert.ToString(obj.dataModel));

                //...複雜的業務邏輯


            }
            catch(Exception ex)
            {
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex.Message);
            }

            return Request.CreateResponse<TB_Product>(HttpStatusCode.OK, oModel);

        }

假如如今在執行try裏面複雜業務邏輯的時候發生了異常,咱們捕獲到了異常而後向客戶端返回HttpError對象,這個對象裏面包含咱們自定義的錯誤訊息,若是正常則返回HttpResponseMessage對象。

若是請求異常:

若是請求正常

4、總結

 以上三種異常的處理方法,能夠根據不一樣的場景選擇使用。

  • 若是項目對異常處理要求並不高,只須要記錄好異常日誌便可,那麼使用異常篩選器就可以搞定
  • 若是項目須要對不一樣的異常,客戶端作不一樣的處理。而這個時候使用異常篩選器不能詳盡全部的異常,可能使用HttpResponseException對象是更好的選擇,定義更加精細的異常和異常描述。
  • 對於什麼時候使用HttpError,又什麼時候使用HttpResponseMessage,能夠參考上文三裏面用法。
  • 固然實際項目中極可能以上兩種或者三種同時使用。

上文經過一些簡單的示例介紹了下WebApi裏面異常的處理機制,可能不夠深刻,但對於通常項目的異常處理基本夠用。其實有一點博主尚未想明白,對於構造函數裏面的異常該如何統一捕獲呢?經過異常篩選器是捕獲不到的,不知道園友們有沒有什麼更好的辦法,不吝賜教,感謝感謝!若是本文能幫到你,不妨推薦下,您的推薦是博主繼續總結的動力!

相關文章
相關標籤/搜索