ASP.NET Web API中的例外狀況處理

本文說明 ASP.NET Web API中的錯誤和例外狀況處理。

HttpResponseException

如果 Web API 控制器擲回未攔截的例外狀況,會發生什麼事? 根據預設,大部分的例外狀況都會轉譯成 HTTP 回應,狀態碼為 500,內部伺服器錯誤。

HttpResponseException類型是特殊案例。 此例外狀況會傳回您在例外狀況建構函式中指定的任何 HTTP 狀態碼。 例如,如果 id 參數無效,下列方法會傳回 404, Not Found。

public Product GetProduct(int id)
{
    Product item = repository.Get(id);
    if (item == null)
    {
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }
    return item;
}

若要進一步控制回應,您也可以建構整個回應訊息,並將其包含在 HttpResponseException 中:

public Product GetProduct(int id)
{
    Product item = repository.Get(id);
    if (item == null)
    {
        var resp = new HttpResponseMessage(HttpStatusCode.NotFound)
        {
            Content = new StringContent(string.Format("No product with ID = {0}", id)),
            ReasonPhrase = "Product ID Not Found"
        };
        throw new HttpResponseException(resp);
    }
    return item;
}

例外狀況篩選條件

您可以藉由撰寫 例外狀況篩選來自訂 Web API 處理例外狀況的方式。 當控制器方法擲回 不是HttpResponseException 例外狀況的任何未處理的例外狀況時,就會執行例外狀況篩選。 HttpResponseException類型是特殊案例,因為它專為傳回 HTTP 回應而設計。

例外狀況篩選會實作 System.Web.Http.Filters.IExceptionFilter 介面。 撰寫例外狀況篩選的最簡單方式是衍生自 System.Web.Http.Filters.ExceptionFilterAttribute 類別,並覆寫 OnException 方法。

注意

ASP.NET Web API中的例外狀況篩選與 ASP.NET MVC 中的篩選準則類似。 不過,它們會分別在個別的命名空間和函式中宣告。 特別是,MVC 中使用的 HandleErrorAttribute 類別不會處理 Web API 控制器擲回的例外狀況。

以下是將 NotImplementedException 例外狀況轉換成 HTTP 狀態碼 501、未實作的篩選準則:

namespace ProductStore.Filters
{
    using System;
    using System.Net;
    using System.Net.Http;
    using System.Web.Http.Filters;

    public class NotImplExceptionFilterAttribute : ExceptionFilterAttribute 
    {
        public override void OnException(HttpActionExecutedContext context)
        {
            if (context.Exception is NotImplementedException)
            {
                context.Response = new HttpResponseMessage(HttpStatusCode.NotImplemented);
            }
        }
    }
}

HttpActionExecutedCoNtext物件的Response屬性包含將傳送至用戶端的 HTTP 回應訊息。

註冊例外狀況篩選

有數種方式可以註冊 Web API 例外狀況篩選︰

  • 透過動作
  • 透過控制器
  • 全域

若要將篩選套用至特定動作,請在動作中新增篩選來做為屬性︰

public class ProductsController : ApiController
{
    [NotImplExceptionFilter]
    public Contact GetContact(int id)
    {
        throw new NotImplementedException("This method is not implemented");
    }
}

若要將篩選套用至控制器上的所有動作,請將篩選新增為控制器類別的屬性:

[NotImplExceptionFilter]
public class ProductsController : ApiController
{
    // ...
}

若要將篩選全域套用至所有 Web API 控制器,請將篩選的實例新增至 GlobalConfiguration.Configuration.Filters 集合。 此集合中的例外狀況篩選會套用至任何 Web API 控制器動作。

GlobalConfiguration.Configuration.Filters.Add(
    new ProductStore.NotImplExceptionFilterAttribute());

如果您使用 「ASP.NET MVC 4 Web 應用程式」專案範本來建立專案,請將 Web API 設定程式碼放在 類別內 WebApiConfig ,其位於 App_Start 資料夾中:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Filters.Add(new ProductStore.NotImplExceptionFilterAttribute());

        // Other configuration code...
    }
}

HttpError

HttpError物件提供一致的方式來傳迴響應本文中的錯誤資訊。 下列範例示範如何在回應本文中使用 HttpError 傳回 HTTP 狀態碼 404 (找不到) 。

public HttpResponseMessage GetProduct(int id)
{
    Product item = repository.Get(id);
    if (item == null)
    {
        var message = string.Format("Product with id = {0} not found", id);
        return Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
    }
    else
    {
        return Request.CreateResponse(HttpStatusCode.OK, item);
    }
}

CreateErrorResponse 是在 System.Net.Http.HttpRequestMessageExtensions 類別中定義的擴充方法。 在內部,CreateErrorResponse會建立HttpError實例,然後建立包含HttpErrorHttpResponseMessage

在此範例中,如果方法成功,它會傳回 HTTP 回應中的產品。 但如果找不到要求的產品,HTTP 回應就會在要求本文中包含 HttpError 。 回應看起來可能如下所示:

HTTP/1.1 404 Not Found
Content-Type: application/json; charset=utf-8
Date: Thu, 09 Aug 2012 23:27:18 GMT
Content-Length: 51

{
  "Message": "Product with id = 12 not found"
}

請注意,在此範例中 ,HttpError 已序列化為 JSON。 使用 HttpError 的優點之一是,它會經歷與任何其他強型別模型相同的 內容交涉 和序列化程式。

HttpError 和模型驗證

針對模型驗證,您可以將模型狀態傳遞至 CreateErrorResponse,以在回應中包含驗證錯誤:

public HttpResponseMessage PostProduct(Product item)
{
    if (!ModelState.IsValid)
    {
        return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
    }

    // Implementation not shown...
}

此範例可能會傳回下列回應:

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Content-Length: 320

{
  "Message": "The request is invalid.",
  "ModelState": {
    "item": [
      "Required property 'Name' not found in JSON. Path '', line 1, position 14."
    ],
    "item.Name": [
      "The Name field is required."
    ],
    "item.Price": [
      "The field Price must be between 0 and 999."
    ]
  }
}

如需模型驗證的詳細資訊,請參閱ASP.NET Web API 中的模型驗證

搭配 HttpResponseException 使用 HttpError

上述範例會從控制器動作傳回 HttpResponseMessage 訊息,但您也可以使用 HttpResponseException 傳回 HttpError。 這可讓您在正常成功案例中傳回強型別模型,同時如果發生錯誤,仍會傳回 HttpError

public Product GetProduct(int id)
{
    Product item = repository.Get(id);
    if (item == null)
    {
        var message = string.Format("Product with id = {0} not found", id);
        throw new HttpResponseException(
            Request.CreateErrorResponse(HttpStatusCode.NotFound, message));
    }
    else
    {
        return item;
    }
}