Tratamento de exceções no ASP.NET Web API

Este artigo descreve o tratamento de erros e exceções em ASP.NET Web API.

HttpResponseException

O que acontece se um controlador de API Web lançar uma exceção não desencasada? Por padrão, a maioria das exceções é convertida em uma resposta HTTP com status código 500, Erro interno do servidor.

O tipo HttpResponseException é um caso especial. Essa exceção retorna qualquer código http status que você especificar no construtor de exceção. Por exemplo, o método a seguir retornará 404, Não Encontrado, se o parâmetro id não for válido.

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

Para obter mais controle sobre a resposta, você também pode construir toda a mensagem de resposta e incluí-la com 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;
}

Filtros de exceção

Você pode personalizar como a API Web lida com exceções escrevendo um filtro de exceção. Um filtro de exceção é executado quando um método de controlador gera qualquer exceção sem tratamento que não seja uma exceção HttpResponseException . O tipo HttpResponseException é um caso especial, pois foi projetado especificamente para retornar uma resposta HTTP.

Os filtros de exceção implementam a interface System.Web.Http.Filters.IExceptionFilter . A maneira mais simples de escrever um filtro de exceção é derivar da classe System.Web.Http.Filters.ExceptionFilterAttribute e substituir o método OnException .

Observação

Os filtros de exceção em ASP.NET Web API são semelhantes aos do MVC ASP.NET. No entanto, eles são declarados em um namespace separado e funcionam separadamente. Em particular, a classe HandleErrorAttribute usada no MVC não manipula exceções geradas por controladores de API Web.

Aqui está um filtro que converte exceções NotImplementedException em HTTP status código 501, Não Implementado:

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

A propriedade Response do objeto HttpActionExecutedContext contém a mensagem de resposta HTTP que será enviada ao cliente.

Registrando filtros de exceção

Há várias maneiras de registrar um filtro de exceção da API Web:

  • por ação
  • pelo controlador
  • globalmente

Para aplicar o filtro em uma ação específica, adicione o filtro como um atributo à ação:

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

Para aplicar o filtro a todas as ações em um controlador, adicione o filtro como um atributo à classe de controlador:

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

Para aplicar o filtro globalmente a todos os controladores de API Web, adicione uma instância do filtro à coleção GlobalConfiguration.Configuration.Filters . Os filtros de exceção nesta coleção aplicam-se a qualquer ação do controlador da API Web.

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

Se você usar o modelo de projeto "aplicativo Web ASP.NET MVC 4" para criar seu projeto, coloque o código de configuração da API Web dentro da WebApiConfig classe , que está localizada na pasta App_Start:

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

        // Other configuration code...
    }
}

HttpError

O objeto HttpError fornece uma maneira consistente de retornar informações de erro no corpo da resposta. O exemplo a seguir mostra como retornar HTTP status código 404 (Não Encontrado) com um HttpError no corpo da resposta.

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 é um método de extensão definido na classe System.Net.Http.HttpRequestMessageExtensions . Internamente, CreateErrorResponse cria uma instância httpError e cria um HttpResponseMessage que contém o HttpError.

Neste exemplo, se o método for bem-sucedido, ele retornará o produto na resposta HTTP. Mas se o produto solicitado não for encontrado, a resposta HTTP conterá um HttpError no corpo da solicitação. A resposta pode ter a seguinte aparência:

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

Observe que o HttpError foi serializado para JSON neste exemplo. Uma vantagem de usar o HttpError é que ele passa pelo mesmo processo de negociação e serialização de conteúdo que qualquer outro modelo fortemente tipado.

Validação de Modelo e HttpError

Para validação de modelo, você pode passar o estado do modelo para CreateErrorResponse, para incluir os erros de validação na resposta:

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

    // Implementation not shown...
}

Este exemplo pode retornar a seguinte resposta:

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."
    ]
  }
}

Para obter mais informações sobre validação de modelo, consulte Validação de modelo em ASP.NET Web API.

Usando HttpError com HttpResponseException

Os exemplos anteriores retornam uma mensagem HttpResponseMessage da ação do controlador, mas você também pode usar HttpResponseException para retornar um HttpError. Isso permite que você retorne um modelo fortemente tipado no caso de sucesso normal, enquanto ainda retorna HttpError se houver um erro:

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