32 votes

Comment désactiver *toute* la gestion des exceptions dans ASP.NET Web API 2 (pour faire de la place pour la mienne) ?

Je veux mettre en place la gestion des exceptions dans un composant middleware, quelque chose comme ceci :

public override async Task Invoke(IOwinContext context)
{
    try
    {
        await Next.Invoke(context);
    }
    catch (Exception ex)
    {
        // Log error and return 500 response
    }
}

Cependant, certaines des exceptions que je voudrais attraper sont attrapées et converties en HttpErrorResponse par le pipeline de l'API Web avant que je puisse les atteindre. Au cours de ce processus, je perds beaucoup de détails sur les erreurs, et je ne peux donc pas obtenir de traces de pile utiles pour le débogage, etc. (le débogueur ne s'arrête même pas lorsque l'exception est levée - je dois parcourir manuellement le code et voir où il échoue...).

J'ai essayé d'ajouter un gestionnaire d'exception personnalisé avec l'implémentation suivante :

public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
    var owinContext = context.Request.GetOwinContext();
    owinContext.Set(Constants.ContextKeys.Exception, context.Exception);
    return Task.FromResult(0);
}

enregistré par config.Services.Replace(typeof(IExceptionHandler), new MyExceptionHandler()); dans ma configuration de démarrage, mais en le regardant après avoir exécuté Next.Invoke(context) par le biais de

context.Get<Exception>(Constants.ContextKeys.Exception);

toujours ne me donne pas tous les détails que je souhaite, et ne parvient pas non plus à s'arrêter au point de défaillance avec le débogueur.

Y a-t-il un moyen pour que je puisse complètement éteindre tous une gestion intégrée des erreurs, de sorte que mon propre intergiciel puisse s'en charger ?

Clarification puisque beaucoup de gens semblent ne pas comprendre ce que je cherche :

  • La gestion intégrée des erreurs dans l'API Web permet d'attraper un peu de (mais pas toutes) les exceptions et les réécrit en 500 réponses.
  • Je veux attraper tous les exceptions, la journalisation, et puis émettre 500 réponses avec les informations que je choisis (pour la plupart d'entre eux, voir le point suivant).
  • Il y a aussi quelques exceptions qui signalent des erreurs de logique commerciale, pour lesquelles je veux renvoyer des erreurs 40x à la place.
  • Je veux que ce soit au sommet du pipeline (de l'application), c'est-à-dire que cela enveloppe tout à un autre moment du cycle de vie de la demande
  • Je veux gérer cela en utilisant OWIN, pour le rendre portable à un éventuel scénario futur d'auto-hébergement (c'est-à-dire qu'il n'est pas écrit dans la pierre que cette application sera toujours hébergée sur IIS - les modules HTTP, Global.asax.cs et autres ne sont pas pertinents ici).

8voto

Tomas Lycken Points 23432

Mise à jour : J'ai fait un blog sur ce sujet . Lors de mes recherches sur l'article de blog, j'ai constaté que certaines améliorations étaient possibles ; j'ai mis à jour les parties pertinentes de cette réponse. Pour plus de détails sur les raisons pour lesquelles je pense que cette solution est meilleure que toutes les autres suggestions ici, ou que le comportement par défaut, lisez l'article en entier :)


J'ai maintenant adopté l'approche suivante, qui semble fonctionner correctement, même si elle ne correspond pas à 100 % à ce que je recherchais :

  • Créer une classe PassthroughExceptionHandler :

    public class PassthroughExceptionHandler : IExceptionHandler
    {
        public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
        {
            // don't just throw the exception; that will ruin the stack trace
            var info = ExceptionDispatchInfo.Capture(context.Exception);
            info.Throw();
        }
    }
  • Que cette classe remplacer le site IExceptionHandler service de l'API Web :

    config.Services.Replace(typeof(IExceptionHandler), new PassthroughExceptionHandler());
  • Créer une classe middleware qui fait ce que je veux :

    public class ExceptionHandlerMiddleware
    {
        public override async Task Invoke(IOwinContext context)
        {
            try
            {
                await Next?.Invoke(context);
            }
            catch (Exception ex)
            {
                // handle and/or log
            }
        }
    }
  • Enregistrez cet intergiciel en premier dans la pile :

    app.Use<ExceptionHandlerMiddleware>()
       .UseStageMarker(PipelineStage.Authenticate)
       // other middlewares omitted for brevity
       .UseStageMarker(PipelineStage.PreHandlerExecute)
       .UseWebApi(config);

Je vais quand même attribuer la prime à quiconque trouvera (prime expirée...) Je suis toujours à la recherche d'une meilleure solution, qui, par exemple, s'interrompe lorsqu'une non géré est déclenchée. (Cette approche fait que VS s'arrête lorsque je relance l'exception dans le gestionnaire, mais la pile d'appel originale est perdue ; je dois placer un point d'arrêt à la ligne d'erreur et déboguer à nouveau pour être capable d'intercepter l'état lorsqu'une exception est lancée).

1voto

MichaelDotKnox Points 359

OWIN n'est pas censé gérer les exceptions de ce type, car l'API Web a sa propre gestion des erreurs intégrée. OWIN est conçu pour être séparé de l'application. Si vous définissez un point d'arrêt sur la méthode HandleAsync de votre gestionnaire d'exception, vous devriez pouvoir inspecter la variable contextuelle et voir les détails de l'exception.

Si vous essayez de faire cela uniquement à des fins de débogage, le fait de placer votre point d'arrêt à cet endroit devrait vous permettre de voir l'exception. Si vous avez besoin d'enregistrer les exceptions, le gestionnaire d'exception est le meilleur endroit pour le faire, à mon avis.

J'espère que cela vous aidera.

1voto

Je ne sais pas si cela vous conviendra, mais j'ai une exigence similaire de renvoyer toutes les erreurs en JSON, même pour les erreurs non trouvées. J'ai créé un contrôleur de base et surchargé l'ExecuteAsync, ce qui me permet de créer mes propres réponses.

public class ControllerBase : ApiController
{
    protected string ClassName = "ControllerBase::";

    public override System.Threading.Tasks.Task<HttpResponseMessage> ExecuteAsync(System.Web.Http.Controllers.HttpControllerContext controllerContext, System.Threading.CancellationToken cancellationToken)
    {
        try
        {
            System.Threading.Tasks.Task<HttpResponseMessage> TaskList = base.ExecuteAsync(controllerContext, cancellationToken);

            if (TaskList.Exception != null && TaskList.Exception.GetBaseException() != null)
            {
                JSONErrorResponse AsyncError = new JSONErrorResponse();
                AsyncError.ExceptionMessage = TaskList.Exception.GetBaseException().Message;
                AsyncError.ErrorMessage = string.Format("Unknown error {0} ExecuteAsync {1}", ClassName ,controllerContext.Request.RequestUri.AbsolutePath);
                AsyncError.HttpErrorCode = HttpStatusCode.BadRequest;

                HttpResponseMessage ErrorResponse = controllerContext.Request.CreateResponse(AsyncError.HttpErrorCode, AsyncError);

                return System.Threading.Tasks.Task.Run<HttpResponseMessage>(() => ErrorResponse);
            }
            return TaskList;
        }
        catch (Exception Error)
        {
            JSONErrorResponse BadParameters = new JSONErrorResponse();
            BadParameters.ExceptionMessage = Error.Message;
            BadParameters.ErrorMessage = string.Format("Method [{0}], or URL [{1}] not found, verify your request", controllerContext.Request.Method.Method, controllerContext.Request.RequestUri.AbsolutePath);
            BadParameters.HttpErrorCode = HttpStatusCode.NotFound;
            HttpResponseMessage ErrorResponse = controllerContext.Request.CreateResponse(BadParameters.HttpErrorCode, BadParameters);

            return System.Threading.Tasks.Task.Run<HttpResponseMessage>(() => ErrorResponse);
        }
    }
}

public class JSONErrorResponse
{
    //Possible message from exception
    public string ExceptionMessage { get; set; }
    //Possible custom error message
    public string ErrorMessage { get; set; }
    //Http error code
    public HttpStatusCode HttpErrorCode { get; set; }
}

1voto

hatcyl Points 1207

Voici quelque chose qui pourrait vous aider :

http://stackoverflow.com/a/21382651/1419853

http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-21#global-error

Essentiellement, il existe un support intégré pour attraper, traiter et modifier les erreurs.

Cela ressemble à quelque chose comme ça :

public class ExceptionLogger : System.Web.Http.ExceptionHandling.ExceptionLogger
{
    Logger _logger;

    public ExceptionLogger(Logger logger)
    {
        _logger = logger;
    }

    public override void Log(ExceptionLoggerContext context)
    {
        _logger.Error(context.ExceptionContext.Exception.ToString());
    }
}

1voto

Vous pouvez également essayer de créer votre propre activateur de contrôleur, avoir votre gestionnaire d'exception personnalisé et essayer d'utiliser ExceptionFilterAttribute.

  1. Créer l'activateur de votre contrôleur

    public class ExceptionHandlingControllerActivator : IHttpControllerActivator
    {
        private readonly IHttpControllerActivator _concreteActivator;
    
        public ExceptionHandlingControllerActivator(IHttpControllerActivator concreteActivator)
        {
            _concreteActivator = concreteActivator;
        }
    
        public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
        {
            try
            {
                return _concreteActivator.Create(request, controllerDescriptor, controllerType);
            }
            catch (Exception ex)
            {
                // do stuff with the exception
                throw new HttpResponseException(request.CreateResponse(HttpStatusCode.InternalServerError, new ResponseModel(ex)));
            }
        }
    }
  2. Créer l'attribut ExceptionFilterAttribute

    public class ExceptionHandlingFilter : ExceptionFilterAttribute
    {
        public override void OnException(HttpActionExecutedContext context)
        {
            // do stuff with the exception
    
            var request = context.Request;
            ResponseModel respMod = null;
    
            // Example: if debug constant is not defined, mask exception, otherwise create normal object with message, inner exception and stacktrace
    
            #if !DEBUG
            respMod = new ResponseModel(context.Exception, context.Exception.Message, true);
            #else
            respMod = new ResponseModel(context.Exception);
            #endif
    
            context.Response = request.CreateResponse(HttpStatusCode.InternalServerError, respMod);
        }
    }
  3. ResponseModel est une classe que je sérialise en JSON à l'aide de formateurs et que je renvoie par toutes les réponses du contrôleur, de sorte que le client puisse identifier les données d'erreur ainsi que la réponse réussie en plus du code d'état HTTP.

    config.Formatters.Clear(); // do not need any other
    config.Formatters.Add(new JsonMediaTypeFormatter());
  4. Câbler

    // ... [cut] ...            
    config.Filters.Add(new ExceptionHandlingFilter());
    // ... [cut] ...
    config.Services.Replace(typeof(IHttpControllerActivator),
        new ExceptionHandlingControllerActivator(config.Services.GetHttpControllerActivator())
    );
    // ... [cut] ...
    app.UseWebApi(config);

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X