web-dev-qa-db-fra.com

intercepte toutes les exceptions non gérées dans ASP.NET Web Api

Comment attraper les exceptions toutes non gérées qui se produisent dans Web Api ASP.NET afin de pouvoir les consigner?

Jusqu'ici j'ai essayé:

  • Créez et enregistrez une ExceptionHandlingAttribute
  • Implémenter une méthode Application_Error dans Global.asax.cs
  • S'abonner à AppDomain.CurrentDomain.UnhandledException
  • S'abonner à TaskScheduler.UnobservedTaskException

ExceptionHandlingAttribute gère avec succès les exceptions émises dans les méthodes d'action du contrôleur et les filtres d'action, mais les autres exceptions ne sont pas gérées, par exemple:

  • Exceptions levées lorsqu'une IQueryable renvoyée par une méthode d'action ne s'exécute pas
  • Exceptions levées par un gestionnaire de messages (à savoir HttpConfiguration.MessageHandlers)
  • Exceptions levées lors de la création d'une instance de contrôleur

Fondamentalement, si une exception provoque le renvoi d’une erreur 500 Internal Server au client, je souhaite qu’elle soit consignée. La mise en œuvre de Application_Error a bien fonctionné dans Web Forms et MVC. Que puis-je utiliser dans Web Api?

107
Joe Daley

Ceci est maintenant possible avec WebAPI 2.1 (voir le Nouveautés ):

Créez une ou plusieurs implémentations de IExceptionLogger. Par exemple:

public class TraceExceptionLogger : ExceptionLogger
{
    public override void Log(ExceptionLoggerContext context)
    {
        Trace.TraceError(context.ExceptionContext.Exception.ToString());
    }
}

Inscrivez-vous ensuite avec la configuration HttpConfiguration de votre application, à l'intérieur d'un rappel de configuration, comme suit:

config.Services.Add(typeof(IExceptionLogger), new TraceExceptionLogger());

ou directement:

GlobalConfiguration.Configuration.Services.Add(typeof(IExceptionLogger), new TraceExceptionLogger());
149
decates

Pour répondre à ma propre question, ce n'est pas possible!

La gestion de toutes les exceptions qui provoquent des erreurs de serveur internes semble être une fonctionnalité de base que devrait avoir l'API Web. J'ai donc demandé à Microsoft de créer un gestionnaire d'erreurs Global pour l'API Web:

https://aspnetwebstack.codeplex.com/workitem/1001

Si vous êtes d'accord, allez sur ce lien et votez pour!

En attendant, l'excellent article ASP.NET Web API Exception Handling montre différentes manières de détecter certaines catégories d'erreur. C’est plus compliqué qu’il ne devrait être, et cela n’attrape pas les toutes erreurs de serveur, mais c’est la meilleure approche disponible à ce jour.

Mise à jour: La gestion globale des erreurs est maintenant implémentée et disponible dans les versions nocturnes! Il sera publié dans ASP.NET MVC v5.1. Voici comment cela fonctionnera: https://aspnetwebstack.codeplex.com/wikipage?title=Global%20Error%20Handling

18
Joe Daley

La réponse de Yuval permet de personnaliser les réponses aux exceptions non gérées interceptées par l'API Web et non pour la journalisation, comme indiqué sur le lien page . Reportez-vous à la section Quand utiliser de la page pour plus de détails. L'enregistreur est toujours appelé, mais le gestionnaire n'est appelé que lorsqu'une réponse peut être envoyée. En bref, utilisez logger pour vous connecter et le gestionnaire pour personnaliser la réponse.

En passant, j'utilise Assembly v5.2.3 et la classe ExceptionHandler n'a pas la méthode HandleCore. L'équivalent, je pense, est Handle. Cependant, le sous-classement ExceptionHandler (comme dans la réponse de Yuval) ne fonctionne pas. Dans mon cas, je dois implémenter IExceptionHandler comme suit.

internal class OopsExceptionHandler : IExceptionHandler
{
    private readonly IExceptionHandler _innerHandler;

    public OopsExceptionHandler (IExceptionHandler innerHandler)
    {
        if (innerHandler == null)
            throw new ArgumentNullException(nameof(innerHandler));

        _innerHandler = innerHandler;
    }

    public IExceptionHandler InnerHandler
    {
        get { return _innerHandler; }
    }

    public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
    {
        Handle(context);

        return Task.FromResult<object>(null);
    }

    public void Handle(ExceptionHandlerContext context)
    {
        // Create your own custom result here...
        // In dev, you might want to null out the result
        // to display the YSOD.
        // context.Result = null;
        context.Result = new InternalServerErrorResult(context.Request);
    }
}

Notez que, contrairement à l'enregistreur, vous enregistrez votre gestionnaire en remplaçant le gestionnaire par défaut, sans l'ajouter.

config.Services.Replace(typeof(IExceptionHandler),
    new OopsExceptionHandler(config.Services.GetExceptionHandler()));
17
Duoc Tran

Vous pouvez également créer un gestionnaire d'exception global en implémentant l'interface IExceptionHandler (ou hériter de la classe de base ExceptionHandler). Ce sera le dernier à être appelé dans la chaîne d'exécution, après avoir tous enregistré IExceptionLogger:

IExceptionHandler gère toutes les exceptions non gérées de tous les contrôleurs. C'est le dernier de la liste. Si une exception se produit, le fichier IExceptionLogger sera appelé en premier, puis le contrôleur ExceptionFilters et, le cas échéant, IExceptionHandler la mise en oeuvre.

public class OopsExceptionHandler : ExceptionHandler
{
    public override void HandleCore(ExceptionHandlerContext context)
    {
        context.Result = new TextPlainErrorResult
        {
            Request = context.ExceptionContext.Request,
            Content = "Oops! Sorry! Something went wrong."        
        };
    }

    private class TextPlainErrorResult : IHttpActionResult
    {
        public HttpRequestMessage Request { get; set; }

        public string Content { get; set; }

        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            HttpResponseMessage response = 
                             new HttpResponseMessage(HttpStatusCode.InternalServerError);
            response.Content = new StringContent(Content);
            response.RequestMessage = Request;
            return Task.FromResult(response);
        }
    }
}

Plus sur cela ici .

7
Yuval Itzchakov

Je pensais que ma nouvelle méthode global.asax.Application_Error n'était pas systématiquement appelée pour les exceptions non gérées dans notre code hérité. 

Ensuite, j'ai trouvé quelques blocs try-catch au milieu de la pile d'appels, appelés Response.Write dans le texte de l'exception. C'était ça. Le texte affiché à l’écran a ensuite tué l’exception pierre morte.

0
Resource