Lancer HttpResponseException ou renvoyer Request.CreateErrorResponse?

172

Après avoir lu un article sur la gestion des exceptions dans l'API Web ASP.NET, je ne sais pas trop quand lever une exception ou renvoyer une réponse d'erreur. Je me demande également s'il est possible de modifier la réponse lorsque votre méthode renvoie un modèle spécifique au domaine au lieu de HttpResponseMessage...

Donc, pour récapituler, voici mes questions suivies d'un code avec des numéros de cas:

Des questions

Questions concernant le cas n ° 1

  1. Dois-je toujours utiliser à la HttpResponseMessageplace d'un modèle de domaine concret, afin que le message puisse être personnalisé?
  2. Le message peut-il être personnalisé si vous renvoyez un modèle de domaine concret?

Questions concernant le cas n ° 2,3,4

  1. Dois-je lancer une exception ou renvoyer une réponse d'erreur? Si la réponse est "cela dépend", pouvez-vous donner des situations / exemples sur le moment où utiliser l'un ou l'autre.
  2. Quelle est la différence entre lancer HttpResponseExceptionvs Request.CreateErrorResponse? La sortie vers le client semble identique ...
  3. Dois-je toujours utiliser HttpErrorpour "envelopper" les messages de réponse dans les erreurs (que l'exception soit levée ou que la réponse d'erreur soit renvoyée)?

Exemples de cas

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

Mettre à jour

Pour aider à démontrer davantage les cas # 2, 3, 4, l'extrait de code suivant met en évidence plusieurs options qui "peuvent se produire" lorsqu'un client n'est pas trouvé ...

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);
}
zam6ak
la source
6
@Mike Wasson En tant qu'auteur de l'article lié, quelle approche adopteriez-vous?
zam6ak

Réponses:

102

L'approche que j'ai adoptée consiste simplement à lancer des exceptions à partir des actions du contrôleur api et à enregistrer un filtre d'exception qui traite l'exception et définit une réponse appropriée sur le contexte d'exécution de l'action.

Le filtre expose une interface fluide qui fournit un moyen d'enregistrer des gestionnaires pour des types spécifiques d'exceptions avant d'enregistrer le filtre avec une configuration globale.

L'utilisation de ce filtre permet une gestion centralisée des exceptions au lieu de la répartir entre les actions du contrôleur. Il y a cependant des cas où je vais attraper des exceptions dans l'action du contrôleur et retourner une réponse spécifique si cela n'a pas de sens de centraliser la gestion de cette exception particulière.

Exemple d'enregistrement de filtre:

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

Le code source peut également être trouvé ici .

Oppositionnel
la source
Hou la la! :) Cela peut être un peu trop pour les petits projets, mais toujours très sympa ... BTW, pourquoi CreateResponse au lieu de CreateErrorResponse dans DefaultHandler?
zam6ak
J'essayais de séparer les détails de l'erreur (sérialisés dans le corps) de la phrase de raison; mais vous pouvez certainement utiliser CreateErrorResponse si cela a plus de sens, comme dans le cas de la liaison de modèle.
Opposition du
1
Comme vous pouvez enregistrer le filtre avec une seule ligne de code, je pense qu'il convient à presque tous les types de projets. Nous avons le filtre dans une bibliothèque de classes qui est publiée sur notre flux NuGet interne, donc il est facile à utiliser pour les développeurs.
Opposition du
Qu'est-ce que vous utilisez pour les gardes (maison ou tierce partie)?
zam6ak du
Hoemgrown, j'ai supprimé son utilisation dans l'exemple ci-dessus. La classe Guard fournit un ensemble de méthodes statiques qui se gardent ou vérifient qu'une assertion a été satisfaite. Voir codepaste.net/5oc1if (Guard) et codepaste.net/nsrsei (DelegateInfo) si vous voulez l'implémentation.
Opposition du
24

Si vous ne retournez pas HttpResponseMessage et que vous retournez directement des classes d'entité / modèle, une approche que j'ai trouvée utile consiste à ajouter la fonction utilitaire suivante à mon contrôleur

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

et appelez-le simplement avec le code d'état et le message appropriés

Joe King
la source
4
C'est la bonne réponse, elle est livrée avec le format "Message" comme paire clé valeur dans le corps. C'est généralement comme ça que je vois les autres frameworks et langages le faire
MobileMon
J'ai une petite question sur cette approche. Je consomme le message en utilisant la syntaxe {{}} dans la page angularJS. Si je laisse les retours chariot, ils apparaissent comme n \ r \ dans le message. Quelle est la bonne façon de les préserver?
Naomi
J'ai essayé cette approche. Je l'ai fait throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid Request Format!")), mais dans Fiddler, il affiche le statut 500 (pas 400). Une idée pourquoi?
Sam
la différence est la fonction parente de l'erreur. Voici la ThrowResponseException pour toute exception dans l'application. Mais devrait être la vraie fonction de lancer l'exception ...
Serge
15

Cas 1

  1. Pas nécessairement, il y a d'autres endroits dans le pipeline pour modifier la réponse (filtres d'action, gestionnaires de messages).
  2. Voir ci-dessus - mais si l'action renvoie un modèle de domaine, vous ne pouvez pas modifier la réponse à l' intérieur de l'action.

Cas # 2-4

  1. Les principales raisons de lancer HttpResponseException sont:
    • si vous renvoyez un modèle de domaine mais devez gérer les cas d'erreur,
    • pour simplifier la logique de votre contrôleur en traitant les erreurs comme des exceptions
  2. Celles-ci devraient être équivalentes; HttpResponseException encapsule un HttpResponseMessage, qui est ce qui est renvoyé en tant que réponse HTTP.

    Par exemple, le cas n ° 2 pourrait être réécrit comme

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

    ... mais si la logique de votre contrôleur est plus compliquée, lever une exception peut simplifier le flux de code.

  3. HttpError vous donne un format cohérent pour le corps de la réponse et peut être sérialisé en JSON / XML / etc, mais ce n'est pas obligatoire. par exemple, vous pouvez ne pas vouloir inclure un corps d'entité dans la réponse, ou vous pourriez vouloir un autre format.

Mike Wasson
la source
L'approche que j'ai adoptée consiste simplement à lancer des exceptions à partir des actions du contrôleur api et j'ai enregistré un filtre d'exception qui traite l'exception et définit une réponse appropriée sur le contexte d'exécution de l'action. Le filtre est «enfichable» de sorte que je puisse enregistrer des gestionnaires pour des types spécifiques d'exceptions avant d'enregistrer le filtre avec une configuration globale. Cela me permet de faire une gestion centralisée des exceptions au lieu de la répartir sur les contrôleurs.
Opposition du
@Oppositional Avez-vous une chance de partager votre filtre d'exception? Peut-être en tant que Gist ou sur un site de partage de code tel que CodePaste?
Paige Cook
@Mike Wasson diriez-vous que la "réponse d'erreur de retour" est une approche plus courante que "lancer une exception"? Je comprends fonctionnellement que le résultat final peut être (est?) Le même, mais je me demande pourquoi ne pas simplement englober toute la logique du contrôleur dans try / catch et renvoyer la réponse d'erreur le cas échéant?
zam6ak
15

Ne lancez pas d'exception HttpResponseException ou ne renvoyez pas un HttpResponesMessage pour les erreurs - sauf si l'intention est de terminer la demande avec ce résultat exact .

Les exceptions HttpResponseException ne sont pas gérées de la même manière que les autres exceptions . Ils ne sont pas pris dans les filtres d'exception . Ils ne sont pas pris dans le gestionnaire d'exceptions . Ils sont un moyen sournois de se glisser dans un HttpResponseMessage tout en mettant fin au flux d'exécution du code actuel.

À moins que le code ne soit un code d'infrastructure reposant sur cette non-gestion spéciale, évitez d' utiliser le type HttpResponseException!

Les HttpResponseMessage ne sont pas des exceptions. Ils ne terminent pas le flux d'exécution du code actuel. Ils ne peuvent pas être filtrés comme exceptions. Ils ne peuvent pas être enregistrés comme exceptions. Ils représentent un résultat valide - même une réponse 500 est "une réponse valide sans exception"!


Simplifiez-vous la vie:

Lorsqu'il y a un cas exceptionnel / d'erreur, lancez une exception .NET normale - ou un type d'exception d'application personnalisé ( ne dérivant pas de HttpResponseException) avec les propriétés `` erreur / réponse http '' souhaitées, telles qu'un code d'état - comme par exception normale manipulation .

Utilisez les filtres d'exception / les gestionnaires d'exceptions / les enregistreurs d'exceptions pour faire quelque chose de approprié dans ces cas exceptionnels: modifier / ajouter des codes d'état? ajouter des identifiants de suivi? inclure des traces de pile? Journal?

En évitant HttpResponseException, la gestion des «cas exceptionnels» est uniformisée et peut être traitée dans le cadre du pipeline exposé! Par exemple, on peut transformer un 'NotFound' en un 404 et un 'ArgumentException' en un 400 et un 'NullReference' en un 500 facilement et uniformément avec des exceptions au niveau de l'application - tout en permettant l'extensibilité pour fournir des "bases" telles que la journalisation des erreurs.

user2864740
la source
2
Je comprends pourquoi ArgumentExceptions dans un contrôleur serait logiquement un 400, mais qu'en est-il de ArgumentExceptions plus profond dans la pile? Il ne serait pas nécessairement correct de les transformer en 400, mais si vous avez un filtre qui convertit tous les ArgumentExceptions en 400, le seul moyen d'éviter cela est d'attraper l'exception dans le contrôleur et de relancer autre chose, ce qui semble pour contourner l'objectif de la gestion uniforme des exceptions dans un filtre ou similaire.
cmeeren
@cmeeren Dans le code que je traitais, la plupart d'entre eux ont attrapé l'exception et l'ont transformée en HttpResponse [Exception / Message] dans chaque méthode Web. Les deux cas sont les mêmes , en ce sens que si le souci est de faire quelque chose de différent avec des exceptions internes, on fait "quelque chose" avec l'exception interne interceptée: je recommande que le résultat soit de lancer une exception d'emballage appropriée qui est toujours traitée plus haut dans le empiler.
user2864740
@cmeeren Après les mises à jour, la plupart de nos points d'entrée Web lancent une dérivée spéciale (non-HttpResponseException, qui a et / est mappée aux codes de réponse appropriés) aux erreurs d'utilisation. Le gestionnaire d'uniforme pourrait faire une inspection de la pile (crapuleux, mais cela fonctionne avec un certain soin) pour déterminer à quel niveau provient l'exception, c'est-à-dire. couvrent 99% des cas qui n'ont pas un traitement plus raffiné - ou répondent simplement avec un 500 pour les erreurs internes. Le point crucial avec HttpResponseException est qu'il contourne le traitement de pipeline utile.
user2864740
9

Un autre cas d'utilisation à la HttpResponseExceptionplace de Response.CreateResponse(HttpStatusCode.NotFound)ou d'un autre code d'état d'erreur est si vous avez des transactions dans des filtres d'action et que vous souhaitez que les transactions soient annulées lors du renvoi d'une réponse d'erreur au client.

L'utilisation Response.CreateResponsen'annulera pas la transaction, alors que la levée d'une exception le fera.

Rob Gray
la source
3

Je tiens à souligner que d'après mon expérience, si vous lancez une HttpResponseException au lieu de renvoyer un HttpResponseMessage dans une méthode webapi 2, si un appel est effectué immédiatement à IIS Express, il expirera ou renverra un 200 mais avec une erreur html dans la réponse. Le moyen le plus simple de tester cela est de faire un appel $ .ajax à une méthode qui lève une HttpResponseException et dans le errorCallBack en ajax faire un appel immédiat à une autre méthode ou même à une simple page http. Vous remarquerez que l'appel immédiat échouera. Si vous ajoutez un point d'arrêt ou un settimeout () dans l'erreur, rappelez pour retarder le deuxième appel d'une seconde ou deux en donnant au serveur le temps de récupérer, cela fonctionne correctement.

Mettre à jour:La cause première du délai d'attente de connexion Ajax wierd est que si un appel ajax est effectué assez rapidement, la même connexion TCP est utilisée. Je levais un éther d'erreur 401 en renvoyant un HttpResonseMessage ou en lançant une exception HTTPResponseException qui a été renvoyée à l'appel ajax du navigateur. Mais avec cet appel, MS renvoyait une erreur d'objet non trouvé car dans Startup.Auth.vb app.UserCookieAuthentication était activé, il essayait donc de retourner intercepter la réponse et d'ajouter une redirection, mais il a commis une erreur avec Object pas Instance d'un objet. Cette erreur était HTML mais a été ajoutée à la réponse après coup, donc ce n'est que si l'appel ajax a été effectué assez rapidement et que la même connexion TCP utilisée a été renvoyée au navigateur, puis ajoutée au début de l'appel suivant. Pour une raison quelconque, Chrome vient d'expirer, Fiddler a pucked en raison du mélange de json et htm mais Firefox a retourné la vraie erreur. Donc, bizarre, mais le renifleur de paquets ou Firefox était le seul moyen de retrouver celui-ci.

Il convient également de noter que si vous utilisez l'aide de l'API Web pour générer une aide automatique et que vous renvoyez HttpResponseMessage, vous devez ajouter un

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

attribuer à la méthode pour que l'aide soit générée correctement. ensuite

return Request.CreateResponse<CustomReturnedType>(objCustomeReturnedType) 

ou sur erreur

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

J'espère que cela aide quelqu'un d'autre qui peut avoir un délai d'expiration aléatoire ou un serveur non disponible immédiatement après avoir lancé une exception HttpResponseException.

Le renvoi d'une HttpResponseException présente également l'avantage de ne pas provoquer l'interruption de Visual Studio sur une exception non gérée, utile lorsque l'erreur renvoyée est que AuthToken doit être actualisé dans une application de page unique.

Mise à jour: Je retire ma déclaration sur l'expiration du délai d'expiration d'IIS Express, cela s'est avéré être une erreur dans mon rappel ajax côté client, il s'avère que Ajax 1.8 retourne $ .ajax () et retourne $ .ajax. (). Then () les deux renvoient la promesse mais pas la même promesse enchaînée then () retourne une nouvelle promesse qui a rendu l'ordre d'exécution erroné. Ainsi, lorsque la promesse then () s'est terminée, c'était un délai d'expiration du script. Bizarre gotcha mais pas un problème express IIS un problème entre le clavier et la chaise.

Andrew DeVries
la source
0

Pour autant que je sache, que vous lanciez une exception ou que vous retourniez Request.CreateErrorResponse, le résultat est le même. Si vous regardez le code source de System.Web.Http.dll, vous en verrez autant. Jetez un œil à ce résumé général et à une solution très similaire que j'ai faite: Web Api, HttpError et le comportement des exceptions

Andy Cohen
la source
0

Dans les situations d'erreur, je voulais renvoyer une classe de détails d'erreur spécifique, dans le format demandé par le client à la place de l'objet happy path.

Je veux que mes méthodes de contrôleur renvoient l'objet happy path spécifique au domaine et lancer une exception dans le cas contraire.

Le problème que j'ai eu est que les constructeurs HttpResponseException n'autorisent pas les objets de domaine.

C'est ce que j'ai finalement trouvé

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

Resultest une classe qui contient des détails d'erreur, tandis que ProviderCollectionmon résultat est heureux.

NickBeaugié
la source
0

J'aime la réponse oppositionnelle

Quoi qu'il en soit, j'avais besoin d'un moyen d'attraper l'exception héritée et cette solution ne répond pas à tous mes besoins.

J'ai donc fini par changer la façon dont il gère OnException et c'est ma version

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

Le noyau est cette boucle où je vérifie si le type d'exception est une sous-classe d'un type enregistré.

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

my2cents

Pas important
la source