ASP.NET Web API 中的异常处理

本文介绍 ASP.NET Web API 中的错误和异常处理。

HttpResponseException

如果 Web API 控制器引发未捕获的异常,会发生什么情况? 默认情况下,大多数异常会转换为状态代码为 500 的 HTTP 响应,即内部服务器错误。

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 中的模型验证

将 HttpError 与 HttpResponseException 配合使用

前面的示例从控制器操作返回 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;
    }
}