Обработка исключений в веб-API ASP.NET

В этой статье описывается обработка ошибок и исключений в веб-API ASP.NET.

HttpResponseException

Что произойдет, если контроллер веб-API создает неперехваченное исключение? По умолчанию большинство исключений претворяются в HTTP-ответ с кодом состояния 500, внутренняя ошибка сервера.

Тип HttpResponseException является особым случаем. Это исключение возвращает любой код состояния HTTP, указанный в конструкторе исключений. Например, следующий метод возвращает значение 404, "Не найдено", если параметр id недопустим.

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;
}

Фильтры исключений

Вы можете настроить обработку исключений в веб-API, написав фильтр исключений. Фильтр исключений выполняется, когда метод контроллера создает любое необработанное исключение, которое не является исключением HttpResponseException . Тип HttpResponseException является особым случаем, так как он предназначен специально для возврата HTTP-ответа.

Фильтры исключений реализуют интерфейс System.Web.Http.Filters.IExceptionFilter . Самый простой способ написать фильтр исключений — наследовать от класса System.Web.Http.Filters.ExceptionFilterAttribute и переопределить метод OnException .

Примечание

Фильтры исключений в веб-API ASP.NET аналогичны фильтрам в ASP.NET MVC. Однако они объявляются в отдельном пространстве имен и выполняются отдельно. В частности, класс HandleErrorAttribute , используемый в MVC, не обрабатывает исключения, создаваемые контроллерами веб-API.

Ниже приведен фильтр, который преобразует исключения NotImplementedException в код состояния HTTP 501, Not Implemented:

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);
            }
        }
    }
}

Свойство Response объекта HttpActionExecutedContext содержит ответное сообщение HTTP, которое будет отправлено клиенту.

Регистрация фильтров исключений

Существует несколько способов регистрации фильтра исключений веб-API:

  • Для действия
  • Для контроллера
  • Глобально

Чтобы применить фильтр к определенному действию, добавьте его в качестве атрибута в действие:

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

Чтобы применить фильтр ко всем действиям на контроллере, добавьте фильтр в качестве атрибута в класс контроллера:

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

Чтобы применить фильтр глобально ко всем контроллерам веб-API, добавьте экземпляр фильтра в коллекцию GlobalConfiguration.Configuration.Filters . В этой коллекции фильтры исключений применяются к любому действию контроллера веб-API.

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

Если вы используете шаблон проекта "веб-приложение ASP.NET MVC 4" для создания проекта, поместите код конфигурации веб-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 предоставляет согласованный способ возврата сведений об ошибке в тексте ответа. В следующем примере показано, как вернуть код состояния HTTP 404 (Не найдено) с httpError в тексте ответа.

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 , а затем создает HttpResponseMessage , содержащий httpError.

В этом примере, если метод выполнен успешно, он возвращает продукт в 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."
    ]
  }
}

Дополнительные сведения о проверке модели см. в статье Проверка модели в веб-API ASP.NET.

Использование 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;
    }
}