Generare HttpResponseException o restituire Request.CreateErrorResponse?


172

Dopo aver esaminato un articolo Gestione delle eccezioni nell'API Web ASP.NET, sono un po 'confuso su quando lanciare un'eccezione e restituire una risposta di errore. Mi chiedo anche se è possibile modificare la risposta quando il metodo restituisce un modello specifico di dominio anziché HttpResponseMessage...

Quindi, per ricapitolare qui sono le mie domande seguite da un codice con i casi #:

Domande

Domande relative al caso n. 1

  1. Devo usare sempre HttpResponseMessageanziché un modello di dominio concreto, in modo che il messaggio possa essere personalizzato?
  2. È possibile personalizzare il messaggio se si restituisce un modello di dominio concreto?

Domande relative al caso n. 2,3,4

  1. Devo generare un'eccezione o restituire una risposta di errore? Se la risposta è "dipende", puoi fornire situazioni / esempi su quando usare l'uno contro l'altro.
  2. Qual'è la differenza tra throwing HttpResponseExceptionvs Request.CreateErrorResponse? L'output al client sembra identico ...
  3. Devo sempre utilizzare HttpErrorper "avvolgere" i messaggi di risposta in errori (se viene generata l'eccezione o restituita la risposta all'errore)?

Esempi di casi

// CASE #1
public Customer Get(string id)
{
    var customer = _customerService.GetById(id);
    if (customer == null)
    {
        var notFoundResponse = new HttpResponseMessage(HttpStatusCode.NotFound);
        throw new HttpResponseException(notFoundResponse);
    }
    //var response = Request.CreateResponse(HttpStatusCode.OK, customer);
    //response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
    return customer;
}        

// CASE #2
public HttpResponseMessage Get(string id)
{
    var customer = _customerService.GetById(id);
    if (customer == null)
    {
        var notFoundResponse = new HttpResponseMessage(HttpStatusCode.NotFound);
        throw new HttpResponseException(notFoundResponse);
    }
    var response = Request.CreateResponse(HttpStatusCode.OK, customer);
    response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
    return response;
}

// CASE #3
public HttpResponseMessage Get(string id)
{
    var customer = _customerService.GetById(id);
    if (customer == null)
    {
        var message = String.Format("customer with id: {0} was not found", id);
        var errorResponse = Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
        throw new HttpResponseException(errorResponse);
    }
    var response = Request.CreateResponse(HttpStatusCode.OK, customer);
    response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
    return response;
}

// CASE #4
public HttpResponseMessage Get(string id)
{
    var customer = _customerService.GetById(id);
    if (customer == null)
    {
        var message = String.Format("customer with id: {0} was not found", id);
        var httpError = new HttpError(message);
        return Request.CreateErrorResponse(HttpStatusCode.NotFound, httpError);
    }
    var response = Request.CreateResponse(HttpStatusCode.OK, customer);
    response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
    return response;
}

Aggiornare

Per aiutare a dimostrare ulteriormente i casi n. 2,3,4 il seguente frammento di codice evidenzia diverse opzioni che "possono accadere" quando non viene trovato un cliente ...

if (customer == null)
{
    // which of these 4 options is the best strategy for Web API?

    // option 1 (throw)
    var notFoundMessage = new HttpResponseMessage(HttpStatusCode.NotFound);
    throw new HttpResponseException(notFoundMessage);

    // option 2 (throw w/ HttpError)
    var message = String.Format("Customer with id: {0} was not found", id);
    var httpError = new HttpError(message);
    var errorResponse = Request.CreateErrorResponse(HttpStatusCode.NotFound, httpError);
    throw new HttpResponseException(errorResponse);

    // option 3 (return)
    var message = String.Format("Customer with id: {0} was not found", id);
    return Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
    // option 4 (return w/ HttpError)
    var message = String.Format("Customer with id: {0} was not found", id);
    var httpError = new HttpError(message);
    return Request.CreateErrorResponse(HttpStatusCode.NotFound, httpError);
}

6
@Mike Wasson Come autore dell'articolo collegato, quale approccio seguiresti?
zam6ak,

Risposte:


102

L'approccio che ho adottato è quello di generare eccezioni dalle azioni del controller API e di registrare un filtro delle eccezioni che elabora l'eccezione e imposta una risposta appropriata sul contesto di esecuzione dell'azione.

Il filtro espone un'interfaccia fluida che fornisce un mezzo per registrare gestori per tipi specifici di eccezioni prima di registrare il filtro con la configurazione globale.

L'uso di questo filtro consente la gestione centralizzata delle eccezioni invece di diffonderla tra le azioni del controller. Vi sono tuttavia casi in cui rileverò eccezioni all'interno dell'azione del controller e restituirò una risposta specifica se non ha senso centralizzare la gestione di quella particolare eccezione.

Esempio di registrazione del filtro:

GlobalConfiguration.Configuration.Filters.Add(
    new UnhandledExceptionFilterAttribute()
    .Register<KeyNotFoundException>(HttpStatusCode.NotFound)

    .Register<SecurityException>(HttpStatusCode.Forbidden)

    .Register<SqlException>(
        (exception, request) =>
        {
            var sqlException = exception as SqlException;

            if (sqlException.Number > 50000)
            {
                var response            = request.CreateResponse(HttpStatusCode.BadRequest);
                response.ReasonPhrase   = sqlException.Message.Replace(Environment.NewLine, String.Empty);

                return response;
            }
            else
            {
                return request.CreateResponse(HttpStatusCode.InternalServerError);
            }
        }
    )
);

Classe UnhandledExceptionFilterAttribute:

using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Web.Http.Filters;

namespace Sample
{
    /// <summary>
    /// Represents the an attribute that provides a filter for unhandled exceptions.
    /// </summary>
    public class UnhandledExceptionFilterAttribute : ExceptionFilterAttribute
    {
        #region UnhandledExceptionFilterAttribute()
        /// <summary>
        /// Initializes a new instance of the <see cref="UnhandledExceptionFilterAttribute"/> class.
        /// </summary>
        public UnhandledExceptionFilterAttribute() : base()
        {

        }
        #endregion

        #region DefaultHandler
        /// <summary>
        /// Gets a delegate method that returns an <see cref="HttpResponseMessage"/> 
        /// that describes the supplied exception.
        /// </summary>
        /// <value>
        /// A <see cref="Func{Exception, HttpRequestMessage, HttpResponseMessage}"/> delegate method that returns 
        /// an <see cref="HttpResponseMessage"/> that describes the supplied exception.
        /// </value>
        private static Func<Exception, HttpRequestMessage, HttpResponseMessage> DefaultHandler = (exception, request) =>
        {
            if(exception == null)
            {
                return null;
            }

            var response            = request.CreateResponse<string>(
                HttpStatusCode.InternalServerError, GetContentOf(exception)
            );
            response.ReasonPhrase   = exception.Message.Replace(Environment.NewLine, String.Empty);

            return response;
        };
        #endregion

        #region GetContentOf
        /// <summary>
        /// Gets a delegate method that extracts information from the specified exception.
        /// </summary>
        /// <value>
        /// A <see cref="Func{Exception, String}"/> delegate method that extracts information 
        /// from the specified exception.
        /// </value>
        private static Func<Exception, string> GetContentOf = (exception) =>
        {
            if (exception == null)
            {
                return String.Empty;
            }

            var result  = new StringBuilder();

            result.AppendLine(exception.Message);
            result.AppendLine();

            Exception innerException = exception.InnerException;
            while (innerException != null)
            {
                result.AppendLine(innerException.Message);
                result.AppendLine();
                innerException = innerException.InnerException;
            }

            #if DEBUG
            result.AppendLine(exception.StackTrace);
            #endif

            return result.ToString();
        };
        #endregion

        #region Handlers
        /// <summary>
        /// Gets the exception handlers registered with this filter.
        /// </summary>
        /// <value>
        /// A <see cref="ConcurrentDictionary{Type, Tuple}"/> collection that contains 
        /// the exception handlers registered with this filter.
        /// </value>
        protected ConcurrentDictionary<Type, Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>> Handlers
        {
            get
            {
                return _filterHandlers;
            }
        }
        private readonly ConcurrentDictionary<Type, Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>> _filterHandlers = new ConcurrentDictionary<Type, Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>>();
        #endregion

        #region OnException(HttpActionExecutedContext actionExecutedContext)
        /// <summary>
        /// Raises the exception event.
        /// </summary>
        /// <param name="actionExecutedContext">The context for the action.</param>
        public override void OnException(HttpActionExecutedContext actionExecutedContext)
        {
            if(actionExecutedContext == null || actionExecutedContext.Exception == null)
            {
                return;
            }

            var type    = actionExecutedContext.Exception.GetType();

            Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> registration = null;

            if (this.Handlers.TryGetValue(type, out registration))
            {
                var statusCode  = registration.Item1;
                var handler     = registration.Item2;

                var response    = handler(
                    actionExecutedContext.Exception.GetBaseException(), 
                    actionExecutedContext.Request
                );

                // Use registered status code if available
                if (statusCode.HasValue)
                {
                    response.StatusCode = statusCode.Value;
                }

                actionExecutedContext.Response  = response;
            }
            else
            {
                // If no exception handler registered for the exception type, fallback to default handler
                actionExecutedContext.Response  = DefaultHandler(
                    actionExecutedContext.Exception.GetBaseException(), actionExecutedContext.Request
                );
            }
        }
        #endregion

        #region Register<TException>(HttpStatusCode statusCode)
        /// <summary>
        /// Registers an exception handler that returns the specified status code for exceptions of type <typeparamref name="TException"/>.
        /// </summary>
        /// <typeparam name="TException">The type of exception to register a handler for.</typeparam>
        /// <param name="statusCode">The HTTP status code to return for exceptions of type <typeparamref name="TException"/>.</param>
        /// <returns>
        /// This <see cref="UnhandledExceptionFilterAttribute"/> after the exception handler has been added.
        /// </returns>
        public UnhandledExceptionFilterAttribute Register<TException>(HttpStatusCode statusCode) 
            where TException : Exception
        {

            var type    = typeof(TException);
            var item    = new Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>(
                statusCode, DefaultHandler
            );

            if (!this.Handlers.TryAdd(type, item))
            {
                Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> oldItem = null;

                if (this.Handlers.TryRemove(type, out oldItem))
                {
                    this.Handlers.TryAdd(type, item);
                }
            }

            return this;
        }
        #endregion

        #region Register<TException>(Func<Exception, HttpRequestMessage, HttpResponseMessage> handler)
        /// <summary>
        /// Registers the specified exception <paramref name="handler"/> for exceptions of type <typeparamref name="TException"/>.
        /// </summary>
        /// <typeparam name="TException">The type of exception to register the <paramref name="handler"/> for.</typeparam>
        /// <param name="handler">The exception handler responsible for exceptions of type <typeparamref name="TException"/>.</param>
        /// <returns>
        /// This <see cref="UnhandledExceptionFilterAttribute"/> after the exception <paramref name="handler"/> 
        /// has been added.
        /// </returns>
        /// <exception cref="ArgumentNullException">The <paramref name="handler"/> is <see langword="null"/>.</exception>
        public UnhandledExceptionFilterAttribute Register<TException>(Func<Exception, HttpRequestMessage, HttpResponseMessage> handler) 
            where TException : Exception
        {
            if(handler == null)
            {
              throw new ArgumentNullException("handler");
            }

            var type    = typeof(TException);
            var item    = new Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>(
                null, handler
            );

            if (!this.Handlers.TryAdd(type, item))
            {
                Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> oldItem = null;

                if (this.Handlers.TryRemove(type, out oldItem))
                {
                    this.Handlers.TryAdd(type, item);
                }
            }

            return this;
        }
        #endregion

        #region Unregister<TException>()
        /// <summary>
        /// Unregisters the exception handler for exceptions of type <typeparamref name="TException"/>.
        /// </summary>
        /// <typeparam name="TException">The type of exception to unregister handlers for.</typeparam>
        /// <returns>
        /// This <see cref="UnhandledExceptionFilterAttribute"/> after the exception handler 
        /// for exceptions of type <typeparamref name="TException"/> has been removed.
        /// </returns>
        public UnhandledExceptionFilterAttribute Unregister<TException>()
            where TException : Exception
        {
            Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> item = null;

            this.Handlers.TryRemove(typeof(TException), out item);

            return this;
        }
        #endregion
    }
}

Il codice sorgente può anche essere trovato qui .


Wow! :) Questo potrebbe essere un po 'troppo per i progetti più piccoli, ma comunque molto bello ... A proposito, perché CreateResponse invece di CreateErrorResponse in DefaultHandler?
zam6ak,

Stavo tentando di separare i dettagli dell'errore (serializzati nel corpo) dalla frase del motivo; ma potresti certamente usare CreateErrorResponse se ciò avesse più senso come nel caso dell'associazione del modello.
Opposizione il

1
Dato che puoi registrare il filtro con una sola riga di codice, penso che sia adatto a quasi tutti i tipi di progetto. Abbiamo il filtro in una libreria di classi pubblicata sul nostro feed NuGet interno, quindi è facile da usare per gli sviluppatori.
Opposizione il

Cosa stai usando per le guardie (nostrane o di terze parti)?
zam6ak,

Hoemgrown, ho rimosso il suo uso nell'esempio sopra. La classe Guard fornisce una serie di metodi statici che proteggono o verificano che un'asserzione sia stata soddisfatta. Vedere codepaste.net/5oc1if (Guard) e codepaste.net/nsrsei (DelegateInfo) se si desidera l'implementazione.
Opposizione il

23

Se non stai restituendo HttpResponseMessage e invece restituisci direttamente le classi entità / modello, un approccio che ho trovato utile è quello di aggiungere la seguente funzione di utilità al mio controller

private void ThrowResponseException(HttpStatusCode statusCode, string message)
{
    var errorResponse = Request.CreateErrorResponse(statusCode, message);
    throw new HttpResponseException(errorResponse);
}

e semplicemente chiamalo con il codice di stato e il messaggio appropriati


4
Questa è la risposta giusta, viene fornita con il formato "Messaggio" come coppia chiave-valore nel corpo. In genere è così che vedo altri framework e linguaggi farlo
MobileMon il

Ho una domanda minore su questo approccio. Sto consumando il messaggio utilizzando la sintassi {{}} nella pagina angularJS. Se lascio i ritorni a capo, vengono visualizzati come n \ r \ nel messaggio. Qual è il modo giusto per preservarli?
Naomi,

Ho provato questo approccio. L'ho fatto throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid Request Format!")), ma in Fiddler mostra lo stato 500 (non 400). Qualche idea sul perché?
Sam

la differenza è la funzione genitore dell'errore. Ecco ThrowResponseException per qualsiasi eccezione nell'applicazione. Ma dovrebbe essere la vera funzione che getta l'eccezione ...
Serge

15

Caso 1

  1. Non necessariamente, ci sono altri punti nella pipeline per modificare la risposta (filtri di azione, gestori di messaggi).
  2. Vedi sopra, ma se l'azione restituisce un modello di dominio, non è possibile modificare la risposta all'interno dell'azione.

Casi n. 2-4

  1. I motivi principali per lanciare HttpResponseException sono:
    • se stai restituendo un modello di dominio ma devi gestire casi di errore,
    • per semplificare la logica del controller trattando gli errori come eccezioni
  2. Questi dovrebbero essere equivalenti; HttpResponseException incapsula un HttpResponseMessage, che è ciò che viene restituito come risposta HTTP.

    ad esempio, il caso n. 2 potrebbe essere riscritto come

    public HttpResponseMessage Get(string id)
    {
        HttpResponseMessage response;
        var customer = _customerService.GetById(id);
        if (customer == null)
        {
            response = new HttpResponseMessage(HttpStatusCode.NotFound);
        }
        else
        {
            response = Request.CreateResponse(HttpStatusCode.OK, customer);
            response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
        }
        return response;
    }

    ... ma se la logica del controller è più complicata, generare un'eccezione potrebbe semplificare il flusso del codice.

  3. HttpError ti offre un formato coerente per il corpo della risposta e può essere serializzato su JSON / XML / etc, ma non è necessario. ad esempio, potresti non voler includere un corpo-entità nella risposta o potresti volere un altro formato.


L'approccio che ho adottato è quello di generare eccezioni dalle azioni del controller API e ho registrato un filtro di eccezioni che elabora l'eccezione e imposta una risposta appropriata sul contesto di esecuzione dell'azione. Il filtro è "innestabile" in modo tale da poter registrare i gestori per tipi specifici di eccezioni prima di registrare il filtro con la configurazione globale. Ciò mi consente di eseguire la gestione centralizzata delle eccezioni invece di distribuirla tra i controller.
Opposizione il

@Opposizionale Qualche possibilità che vorresti condividere il filtro delle eccezioni? Forse come Gist o su un sito di code sharing come CodePaste?
Paige Cook,

@Mike Wasson diresti che "risposta all'errore di ritorno" è l'approccio più comune contro "eccezione di lancio"? Comprendo funzionalmente che il risultato finale potrebbe essere (è?) Lo stesso, ma mi chiedo perché non includere l'intera logica del controller in try / catch e restituire la risposta dell'errore nel modo appropriato?
zam6ak,

15

Non generare un HttpResponseException o restituire un HttpResponesMessage per errori, tranne se si intende terminare la richiesta con il risultato esatto .

Le HttpResponseException non sono gestite come le altre eccezioni . Non vengono catturati nei filtri di eccezione . Non vengono catturati nel Gestore eccezioni . Sono un modo subdolo per inserire un HttpResponseMessage mentre si termina il flusso di esecuzione del codice corrente.

A meno che il codice non sia un codice di infrastruttura che si basa su questa speciale non gestione, evitare di utilizzare il tipo HttpResponseException!

HttpResponseMessage non fa eccezione. Non terminano il flusso di esecuzione del codice corrente. Possono non essere filtrati come eccezioni. Essi possono non essere registrate come eccezioni. Rappresentano un risultato valido - anche una risposta 500 è "una risposta valida senza eccezioni"!


Semplifica la vita:

Quando si verifica un caso eccezionale / errore, andare avanti e generare un'eccezione .NET normale o un tipo di eccezione applicazione personalizzata ( non derivante da HttpResponseException) con le proprietà "errore / risposta" http desiderate come un codice di stato - come da eccezione normale maneggiamento .

Utilizzare i filtri di eccezione / Gestori di eccezioni / Registratori di eccezioni per fare qualcosa di appropriato in questi casi eccezionali: modificare / aggiungere codici di stato? aggiungere identificatori di tracciamento? includere tracce dello stack? log?

Evitando HttpResponseException la gestione del "caso eccezionale" è resa uniforme e può essere gestita come parte della condotta esposta! Ad esempio, si può trasformare un 'NotFound' in un 404 e un 'ArgumentException' in un 400 e un 'NullReference' in un 500 facilmente e uniformemente con eccezioni a livello di applicazione - consentendo al contempo all'estensibilità di fornire "elementi di base" come la registrazione degli errori.


2
Capisco perché ArgumentExceptionin un controller sarebbe logicamente un 400, ma cosa ne pensi di uno ArgumentExceptionpiù profondo nello stack? Non sarebbe necessariamente corretto trasformarli in 400, ma se hai un filtro che la coperta converte tutte le ArgumentExceptions in 400, l'unico modo per evitarlo è catturare l'eccezione nel controller e ri-lanciare qualcos'altro, che sembra per annullare lo scopo della gestione uniforme delle eccezioni in un filtro o simile.
cmeeren,

@cmeeren Nel codice che stavo gestendo, la maggior parte ha catturato l'eccezione e l'ha trasformata in HttpResponse [Eccezione / Messaggio] in ogni metodo web. Entrambi i casi sono gli stessi , nel senso che se la preoccupazione è fare qualcosa di diverso con le eccezioni interne che si fanno facendo "qualcosa" con l'eccezione interna rilevata: consiglio il risultato è di lanciare un'eccezione di wrapping appropriata che è ancora gestita pila.
user2864740,

@cmeeren Dopo gli aggiornamenti, la maggior parte dei nostri punti di accesso Web genera uno speciale derivato (non HttpResponseException, che ha eo è mappato su codici di risposta appropriati) per errori di utilizzo. Il gestore uniforme potrebbe eseguire alcune ispezioni dello stack (icky, ma funziona con una certa cura) per determinare a quale livello provenga l'eccezione, ad es. copre il 99% dei casi che non hanno una gestione più raffinata o semplicemente risponde con un 500 per errori interni. Il punto cruciale con HttpResponseException è che ignora l'elaborazione utile della pipeline.
user2864740,

9

Un altro caso per quando usare HttpResponseExceptioninvece di Response.CreateResponse(HttpStatusCode.NotFound), o altro codice di stato di errore, è se si hanno transazioni nei filtri di azione e si desidera che le transazioni vengano ripristinate quando si restituisce una risposta di errore al client.

Utilizzando Response.CreateResponsenon verrà ripristinata la transazione, mentre verrà generata un'eccezione.


3

Voglio sottolineare che è stata la mia esperienza che se si lancia un HttpResponseException invece di restituire un HttpResponseMessage in un metodo webapi 2, che se una chiamata viene effettuata immediatamente a IIS Express, si verificherà un timeout o restituirà un 200 ma con un errore HTML in la risposta. Il modo più semplice per verificarlo è effettuare una chiamata $ .ajax a un metodo che genera una HttpResponseException e nell'erroreCallBack in ajax effettua una chiamata immediata a un altro metodo o anche a una semplice pagina http. Noterai che la chiamata immediata non riuscirà. Se si aggiunge un punto di interruzione o un settimeout () nell'errore, richiamare per ritardare la seconda chiamata di uno o due secondi dando al server il tempo di ripristinarlo funziona correttamente.

Aggiornare:La causa principale della strana connessione Ajax Il timeout è se una chiamata ajax viene effettuata abbastanza rapidamente viene utilizzata la stessa connessione tcp. Stavo generando un errore dell'errore 401 restituendo un HttpResonseMessage o lanciando un HTTPResponseException che è stato restituito alla chiamata ajax del browser. Ma insieme a quella chiamata MS stava restituendo un errore Object Not Found perché in Startup.Auth.vb app.UserCookieAuthentication era abilitato, quindi stava provando a restituire intercettare la risposta e aggiungere un reindirizzamento, ma era errato con Object non Instance of a Object. Questo errore era html ma è stato aggiunto alla risposta dopo il fatto, quindi solo se la chiamata ajax è stata effettuata abbastanza velocemente e la stessa connessione tcp utilizzata è stata restituita al browser e quindi è stata aggiunta all'inizio della chiamata successiva. Per qualche motivo Chrome è scaduto, il violinista si increspò a causa del mix di json e htm, ma Firefox ha restituito il vero errore. Così strano ma sniffer di pacchetti o firefox era l'unico modo per rintracciarlo.

Inoltre, è necessario notare che se si utilizza la guida dell'API Web per generare la guida automatica e si restituisce HttpResponseMessage, è necessario aggiungere un

[System.Web.Http.Description.ResponseType(typeof(CustomReturnedType))] 

attributo al metodo in modo che la guida venga generata correttamente. Poi

return Request.CreateResponse<CustomReturnedType>(objCustomeReturnedType) 

o in caso di errore

return Request.CreateErrorResponse( System.Net.HttpStatusCode.InternalServerError, new Exception("An Error Ocurred"));

Spero che questo aiuti qualcun altro che potrebbe avere un timeout casuale o un server non disponibile immediatamente dopo aver lanciato una HttpResponseException.

Anche la restituzione di HttpResponseException ha l'ulteriore vantaggio di non causare l'interruzione di Visual Studio su un'eccezione non gestita utile quando l'errore restituito è che AuthToken deve essere aggiornato in un'app a pagina singola.

Aggiornamento: sto ritirando la mia dichiarazione sul timeout di IIS Express, questo è successo per essere un errore nel mio client ajax richiamare che risulta dal fatto che Ajax 1.8 restituisce $ .ajax () e restituisce $ .ajax. (). Then () entrambi restituiscono promessa ma non la stessa promessa concatenata quindi () restituisce una nuova promessa che ha causato l'ordine di esecuzione errato. Quindi, quando la promessa then () è stata completata, è stato un timeout dello script. Strano gotcha ma non un IIS express presenta un problema tra la tastiera e la sedia.


0

Per quanto ne so, se si genera un'eccezione o si restituisce Request.CreateErrorResponse, il risultato è lo stesso. Se guardi il codice sorgente per System.Web.Http.dll, vedrai altrettanto. Dai un'occhiata a questo riepilogo generale e a una soluzione molto simile che ho creato: Web Api, HttpError e il comportamento delle eccezioni


0

In situazioni di errore, volevo restituire una specifica classe di dettagli dell'errore, in qualunque formato richiesto dal client anziché l'oggetto happy path.

Voglio che i metodi del mio controller restituiscano l'oggetto percorso felice specifico del dominio e che generino un'eccezione in caso contrario.

Il problema che ho avuto è stato che i costruttori HttpResponseException non consentono oggetti di dominio.

Questo è quello che alla fine mi è venuta in mente

public ProviderCollection GetProviders(string providerName)
{
   try
   {
      return _providerPresenter.GetProviders(providerName);
   }
   catch (BadInputValidationException badInputValidationException)
   {
     throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest,
                                          badInputValidationException.Result));
   }
}

Result è una classe che contiene dettagli di errore, mentre ProviderCollection è il risultato del mio percorso felice.


0

mi piace risposta opposta

Comunque, avevo bisogno di un modo per catturare l'eccezione ereditata e quella soluzione non soddisfa tutti i miei bisogni.

Così ho finito per cambiare il modo in cui gestisce OnException e questa è la mia versione

public override void OnException(HttpActionExecutedContext actionExecutedContext) {
   if (actionExecutedContext == null || actionExecutedContext.Exception == null) {
      return;
   }

   var type = actionExecutedContext.Exception.GetType();

   Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> registration = null;

   if (!this.Handlers.TryGetValue(type, out registration)) {
      //tento di vedere se ho registrato qualche eccezione che eredita dal tipo di eccezione sollevata (in ordine di registrazione)
      foreach (var item in this.Handlers.Keys) {
         if (type.IsSubclassOf(item)) {
            registration = this.Handlers[item];
            break;
         }
      }
   }

   //se ho trovato un tipo compatibile, uso la sua gestione
   if (registration != null) {
      var statusCode = registration.Item1;
      var handler = registration.Item2;

      var response = handler(
         actionExecutedContext.Exception.GetBaseException(),
         actionExecutedContext.Request
      );

      // Use registered status code if available
      if (statusCode.HasValue) {
         response.StatusCode = statusCode.Value;
      }

      actionExecutedContext.Response = response;
   }
   else {
      // If no exception handler registered for the exception type, fallback to default handler
      actionExecutedContext.Response = DefaultHandler(actionExecutedContext.Exception.GetBaseException(), actionExecutedContext.Request
      );
   }
}

Il nucleo è questo ciclo in cui controllo se il tipo di eccezione è una sottoclasse di un tipo registrato.

foreach (var item in this.Handlers.Keys) {
    if (type.IsSubclassOf(item)) {
        registration = this.Handlers[item];
        break;
    }
}

my2cents

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.