Control de excepciones en ASP.NET ASP.NET Web API

En este artículo se describe el control de errores y excepciones en ASP.NET Web API.

HttpResponseException

¿Qué ocurre si un controlador de Web API produce una excepción no detectada? De forma predeterminada, la mayoría de las excepciones se traducen en una respuesta HTTP con el código de estado 500, Error interno del servidor.

El tipo HttpResponseException es un caso especial. Esta excepción devuelve cualquier código de estado HTTP que especifique en el constructor de excepciones. Por ejemplo, el método siguiente devuelve 404, No encontrado, si el parámetro id no es válido.

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

Para obtener más control sobre la respuesta, también puede construir todo el mensaje de respuesta e incluirlo con 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 excepciones

Puede personalizar cómo la API web controla las excepciones escribiendo un filtro de excepciones. Se ejecuta un filtro de excepción cuando un método de controlador produce cualquier excepción no controlada que no se no una excepción de HttpResponseException. El tipo HttpResponseException es un caso especial, ya que está diseñado específicamente para devolver una respuesta HTTP.

Los filtros de excepción implementan la interfaz System.Web.Http.Filters.IExceptionFilter. La manera más sencilla de escribir un filtro de excepción es derivar de la clase System.Web.Http.Filters.ExceptionFilterAttribute e invalidar el método OnException.

Nota:

Los filtros de excepción de ASP.NET Web API son similares a los de ASP.NET MVC. Sin embargo, se declaran en un espacio de nombres independiente y función por separado. En concreto, la clase HandleErrorAttribute usada en MVC no controla las excepciones producidas por los controladores de Web API.

Este es un filtro que convierte NotImplementedException excepciones en el código de estado HTTP 501, No 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);
            }
        }
    }
}

La propiedad Response del objeto HttpActionExecutedContext contiene el mensaje de respuesta HTTP que se enviará al cliente.

Registro de filtros de excepciones

Hay varias formas de registrar un filtro de excepción API web:

  • Por acción
  • Por controlador
  • Globalmente

Para aplicar el filtro a una acción concreta, agregue el filtro como un atributo a la acción:

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

Para aplicar el filtro a todas las acciones de un controlador, agregue el filtro como atributo a la clase de controlador:

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

Para aplicar el filtro globalmente a todos los controladores de Web API, agregue una instancia del filtro a la colección GlobalConfiguration.Configuration.Filters. Los filtros de excepción de esta colección se aplican a cualquier acción de controlador de API web.

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

Si usa la plantilla de proyecto "ASP.NET aplicación web MVC 4" para crear el proyecto, coloque el código de configuración de la API web dentro de la clase WebApiConfig, que se encuentra en la carpeta App_Start:

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

        // Other configuration code...
    }
}

HttpError

El objeto HttpError proporciona una manera coherente de devolver información de error en el cuerpo de la respuesta. En el ejemplo siguiente se muestra cómo devolver el código de estado HTTP 404 (no encontrado) con un HttpError en el cuerpo de la respuesta.

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 es un método de extensión definido en la clase System.Net.Http.HttpRequestMessageExtensions. Internamente, CreateErrorResponse crea una instancia de HttpError y, a continuación, crea HttpResponseMessage que contiene HttpError.

En este ejemplo, si el método se ejecuta correctamente, devuelve el producto en la respuesta HTTP. Pero si no se encuentra el producto solicitado, la respuesta HTTP contiene un HttpError en el cuerpo de la solicitud. La respuesta podría ser similar a la siguiente:

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

Tenga en cuenta que HttpError se serializó en JSON en este ejemplo. Una ventaja de usar HttpError es que pasa por el mismo proceso de negociación y serialización de contenido que cualquier otro modelo fuertemente tipado.

HttpError y validación del modelo

Para la validación del modelo, puede pasar el estado del modelo a CreateErrorResponse, para incluir los errores de validación en la respuesta:

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

    // Implementation not shown...
}

Este ejemplo podría devolver la siguiente respuesta:

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 obtener más información sobre la validación del modelo, consulte Validación de modelos en ASP.NET Web API.

Uso de HttpError con HttpResponseException

Los ejemplos anteriores devuelven un mensaje de HttpResponseMessage de la acción del controlador, pero también puede usar HttpResponseException para devolver un HttpError. Esto le permite devolver un modelo fuertemente tipado en el caso correcto normal, mientras sigue devolviendo HttpError si se produce un error:

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