Gestion des exceptions dans API Web ASP.NET

Cet article décrit la gestion des erreurs et des exceptions dans API Web ASP.NET.

HttpResponseException

Que se passe-t-il si un contrôleur d’API web lève une exception non interceptée ? Par défaut, la plupart des exceptions sont traduites en réponse HTTP avec status code 500, Erreur interne du serveur.

Le type HttpResponseException est un cas spécial. Cette exception retourne tout code status HTTP que vous spécifiez dans le constructeur d’exception. Par exemple, la méthode suivante retourne 404, Introuvable, si le paramètre id n’est pas valide.

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

Pour plus de contrôle sur la réponse, vous pouvez également construire l’intégralité du message de réponse et l’inclure avec l’exception 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;
}

Filtres d’exception

Vous pouvez personnaliser la façon dont l’API web gère les exceptions en écrivant un filtre d’exception. Un filtre d’exception est exécuté lorsqu’une méthode de contrôleur lève une exception non gérée qui n’est pas une exception HttpResponseException . Le type HttpResponseException est un cas particulier, car il est conçu spécifiquement pour retourner une réponse HTTP.

Les filtres d’exceptions implémentent l’interface System.Web.Http.Filters.IExceptionFilter . Le moyen le plus simple d’écrire un filtre d’exception consiste à dériver de la classe System.Web.Http.Filters.ExceptionFilterAttribute et à remplacer la méthode OnException .

Notes

Les filtres d’exception dans API Web ASP.NET sont similaires à ceux de ASP.NET MVC. Toutefois, elles sont déclarées dans un espace de noms distinct et fonctionnent séparément. En particulier, la classe HandleErrorAttribute utilisée dans MVC ne gère pas les exceptions levées par les contrôleurs d’API web.

Voici un filtre qui convertit les exceptions NotImplementedException en code HTTP status 501, Non implémenté :

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 propriété Response de l’objet HttpActionExecutedContext contient le message de réponse HTTP qui sera envoyé au client.

Inscription de filtres d’exceptions

Plusieurs méthodes pour enregistrer un filtre d’exception d’API web sont possibles :

  • Par action
  • Par contrôleur
  • Globalement

Pour appliquer le filtre à une action spécifique, ajoutez le filtre en tant qu’attribut à l’action :

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

Pour appliquer le filtre à toutes les actions sur un contrôleur, ajoutez le filtre en tant qu’attribut à la classe de contrôleur :

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

Pour appliquer le filtre globalement à tous les contrôleurs d’API web, ajoutez un instance du filtre à la collection GlobalConfiguration.Configuration.Filters. Les filtres d’exception de cette collection s’appliquent à n’importe quelle action de contrôleur d’API web.

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

Si vous utilisez le modèle de projet « application web ASP.NET MVC 4 » pour créer votre projet, placez votre code de configuration d’API web à l’intérieur de la WebApiConfig classe, qui se trouve dans le dossier App_Start :

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

        // Other configuration code...
    }
}

HttpError

L’objet HttpError fournit un moyen cohérent de retourner des informations d’erreur dans le corps de la réponse. L’exemple suivant montre comment renvoyer http status code 404 (introuvable) avec une erreur HttpError dans le corps de la réponse.

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 est une méthode d’extension définie dans la classe System.Net.Http.HttpRequestMessageExtensions . En interne, CreateErrorResponse crée un instance HttpError, puis un httpResponseMessage qui contient httpError.

Dans cet exemple, si la méthode réussit, elle retourne le produit dans la réponse HTTP. Toutefois, si le produit demandé est introuvable, la réponse HTTP contient une erreur HttpError dans le corps de la demande. La réponse peut ressembler à ceci :

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

Notez que HttpError a été sérialisé au format JSON dans cet exemple. L’un des avantages de l’utilisation de HttpError est qu’il passe par le même processus de négociation de contenu et de sérialisation que tout autre modèle fortement typé.

HttpError et validation du modèle

Pour la validation du modèle, vous pouvez passer l’état du modèle à CreateErrorResponse, afin d’inclure les erreurs de validation dans la réponse :

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

    // Implementation not shown...
}

Cet exemple peut renvoyer la réponse suivante :

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

Pour plus d’informations sur la validation du modèle, consultez Validation de modèle dans API Web ASP.NET.

Utilisation de HttpError avec HttpResponseException

Les exemples précédents retournent un message HttpResponseMessage à partir de l’action du contrôleur, mais vous pouvez également utiliser HttpResponseException pour renvoyer une erreur HttpError. Cela vous permet de retourner un modèle fortement typé dans le cas normal de réussite, tout en retournant httpError en cas d’erreur :

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