Gestione delle eccezioni in API Web ASP.NET

Questo articolo descrive la gestione degli errori e delle eccezioni in API Web ASP.NET.

HttpResponseException

Cosa accade se un controller API Web genera un'eccezione non rilevata? Per impostazione predefinita, la maggior parte delle eccezioni viene convertita in una risposta HTTP con codice di stato 500, Errore interno del server.

Il tipo HttpResponseException è un caso speciale. Questa eccezione restituisce qualsiasi codice di stato HTTP specificato nel costruttore dell'eccezione. Ad esempio, il metodo seguente restituisce 404, Not Found, se il parametro id non è valido.

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

Per un maggiore controllo sulla risposta, è anche possibile costruire l'intero messaggio di risposta e includerlo 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;
}

Filtri eccezioni

È possibile personalizzare il modo in cui l'API Web gestisce le eccezioni scrivendo un filtro di eccezione. Un filtro eccezioni viene eseguito quando un metodo controller genera qualsiasi eccezione non gestita che non è un'eccezione HttpResponseException . Il tipo HttpResponseException è un caso speciale, perché è progettato specificamente per la restituzione di una risposta HTTP.

I filtri eccezioni implementano l'interfaccia System.Web.Http.Filters.IExceptionFilter . Il modo più semplice per scrivere un filtro eccezioni consiste nel derivare dalla classe System.Web.Http.Filters.ExceptionFilterAttribute ed eseguire l'override del metodo OnException .

Nota

I filtri delle eccezioni in API Web ASP.NET sono simili a quelli in ASP.NET MVC. Tuttavia, vengono dichiarati separatamente in uno spazio dei nomi e in una funzione separati. In particolare, la classe HandleErrorAttribute usata in MVC non gestisce le eccezioni generate dai controller API Web.

Ecco un filtro che converte le eccezioni NotImplementedException in codice di stato HTTP 501, Non implementato:

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 proprietà Response dell'oggetto HttpActionExecutedContext contiene il messaggio di risposta HTTP che verrà inviato al client.

Registrazione di filtri eccezioni

Esistono diversi modi per registrare un filtro eccezioni API Web:

  • Tramite un'azione
  • Tramite un controller
  • A livello globale

Per applicare il filtro a un'azione specifica, aggiungere il filtro come attributo per l'azione:

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

Per applicare il filtro a tutte le azioni in un controller, aggiungere il filtro come attributo alla classe controller:

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

Per applicare il filtro a livello globale a tutti i controller API Web, aggiungere un'istanza del filtro all'insieme GlobalConfiguration.Configuration.Filters . I filtri eccezioni in questa raccolta si applicano a qualsiasi azione del controller API Web.

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

Se si usa il modello di progetto "ASP.NET applicazione Web MVC 4 MVC 4" per creare il progetto, inserire il codice di configurazione dell'API Web all'interno della WebApiConfig classe , che si trova nella cartella App_Start:

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

        // Other configuration code...
    }
}

HttpError

L'oggetto HttpError offre un modo coerente per restituire le informazioni sull'errore nel corpo della risposta. Nell'esempio seguente viene illustrato come restituire il codice di stato HTTP 404 (Non trovato) con httpError nel corpo della risposta.

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 è un metodo di estensione definito nella classe System.Net.Http.HttpRequestMessageExtensions . Internamente , CreateErrorResponse crea un'istanza HttpError e quindi crea un HttpResponseMessage che contiene HttpError.

In questo esempio, se il metodo ha esito positivo, restituisce il prodotto nella risposta HTTP. Tuttavia, se il prodotto richiesto non viene trovato, la risposta HTTP contiene un errore HttpError nel corpo della richiesta. La risposta potrebbe essere simile alla seguente:

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

Si noti che HttpError è stato serializzato in JSON in questo esempio. Un vantaggio dell'uso di HttpError è che passa attraverso lo stesso processo di negoziazione del contenuto e serializzazione di qualsiasi altro modello fortemente tipizzato.

Convalida di httperrore e modelli

Per la convalida del modello, è possibile passare lo stato del modello a CreateErrorResponse per includere gli errori di convalida nella risposta:

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

    // Implementation not shown...
}

Questo esempio potrebbe restituire la risposta seguente:

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

Per altre informazioni sulla convalida del modello, vedere Convalida del modello in API Web ASP.NET.

Uso di HttpError con HttpResponseException

Gli esempi precedenti restituiscono un messaggio HttpResponseMessage dall'azione del controller, ma è anche possibile usare HttpResponseException per restituire un errore HttpError. Ciò consente di restituire un modello fortemente tipizzato nel normale caso di esito positivo, restituendo comunque HttpError se si verifica un errore:

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