181 votes

Comment puis-je consigner TOUTES les exceptions de manière globale pour une application WebAPI MVC4 en C# ?

Contexte

Je suis en train de développer une couche de service API pour un client et on m'a demandé d'attraper et d'enregistrer toutes les erreurs de manière globale.

Ainsi, alors que quelque chose comme un point de terminaison (ou une action) inconnu(e) est facilement géré en utilisant ELMAH ou en ajoutant quelque chose comme ceci à la section Global.asax :

protected void Application_Error()
{
     Exception unhandledException = Server.GetLastError();
     //do more stuff
}

. . les erreurs non gérées qui ne sont pas liées au routage ne sont pas enregistrées. Par exemple :

public class ReportController : ApiController
{
    public int test()
    {
        var foo = Convert.ToInt32("a");//Will throw error but isn't logged!!
        return foo;
    }
}

J'ai également essayé de définir le [HandleError] de manière globale en enregistrant ce filtre :

filters.Add(new HandleErrorAttribute());

Mais cela ne permet pas non plus d'enregistrer toutes les erreurs.

Problème/Question

Comment puis-je intercepter les erreurs comme celle générée par l'appel de la fonction /test ci-dessus pour que je puisse les enregistrer ? Il semble que cette réponse devrait être évidente, mais j'ai essayé tout ce à quoi j'ai pensé jusqu'à présent.

Idéalement, je voudrais ajouter certains éléments à la journalisation des erreurs, comme l'adresse IP de l'utilisateur demandeur, la date, l'heure, etc. Je voudrais également pouvoir envoyer automatiquement un courriel au personnel d'assistance lorsqu'une erreur est rencontrée. Tout cela, je peux le faire si seulement je peux intercepter ces erreurs quand elles se produisent !

RÉSOLU !

Grâce à Darin Dimitrov, dont j'ai accepté la réponse, j'ai trouvé la solution. WebAPI fait no traiter les erreurs de la même manière qu'un contrôleur MVC ordinaire.

Voici ce qui a fonctionné :

1) Ajoutez un filtre personnalisé à votre espace de noms :

public class ExceptionHandlingAttribute : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext context)
    {
        if (context.Exception is BusinessException)
        {
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
            {
                Content = new StringContent(context.Exception.Message),
                ReasonPhrase = "Exception"
            });

        }

        //Log Critical errors
        Debug.WriteLine(context.Exception);

        throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
        {
            Content = new StringContent("An error occurred, please try again or contact the administrator."),
            ReasonPhrase = "Critical Exception"
        });
    }
}

2) Maintenant, enregistrez le filtre de manière globale dans le fichier WebApiConfig classe :

public static class WebApiConfig
{
     public static void Register(HttpConfiguration config)
     {
         config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{action}/{id}", new { id = RouteParameter.Optional });
         config.Filters.Add(new ExceptionHandlingAttribute());
     }
}

OU vous pouvez ignorer l'enregistrement et décorer un seul contrôleur avec l'attribut [ExceptionHandling] attribut.

0 votes

J'ai le même problème. Les exceptions non gérées sont prises en compte dans l'attribut de filtre d'exception, mais lorsque je lance une nouvelle exception, elle n'est pas prise en compte dans l'attribut de filtre d'exception.

1 votes

Des appels de contrôleur d'api inconnus comme monhost/api/undefinedapicontroller Les erreurs ne sont toujours pas capturées. Le code du filtre Application_error et Exception n'est pas exécuté. Comment les attraper également ?

1 votes

La gestion des erreurs globales a été ajoutée à la WebAPI v2.1. Voir ma réponse ici : stackoverflow.com/questions/17449400/

82voto

Vladimir Frolov Points 2988

En complément des réponses précédentes.

Hier, l'ASP.NET Web API 2.1 a été officiellement libéré .
Il offre une autre possibilité de traiter les exceptions de manière globale.
Les détails sont donnés dans le échantillon .

En bref, vous ajoutez des enregistreurs d'exception globaux et/ou un gestionnaire d'exception global (un seul).
Vous les ajoutez à la configuration :

public static void Register(HttpConfiguration config)
{
  config.MapHttpAttributeRoutes();

  // There can be multiple exception loggers.
  // (By default, no exception loggers are registered.)
  config.Services.Add(typeof(IExceptionLogger), new ElmahExceptionLogger());

  // There must be exactly one exception handler.
  // (There is a default one that may be replaced.)
  config.Services.Replace(typeof(IExceptionHandler), new GenericTextExceptionHandler());
}

Et leur réalisation :

public class ElmahExceptionLogger : ExceptionLogger
{
  public override void Log(ExceptionLoggerContext context)
  {
    ...
  }
}

public class GenericTextExceptionHandler : ExceptionHandler
{
  public override void Handle(ExceptionHandlerContext context)
  {
    context.Result = new InternalServerErrorTextPlainResult(
      "An unhandled exception occurred; check the log for more information.",
      Encoding.UTF8,
      context.Request);
  }
}

2 votes

Cela a parfaitement fonctionné. J'enregistre et je traite simultanément (parce que je récupère le logID et le renvoie pour que l'utilisateur puisse ajouter un commentaire), donc je fixe Resultat à un nouveau ResponseMessageResult. Cela m'embête depuis un moment, merci.

56voto

Darin Dimitrov Points 528142

Si votre API web est hébergée à l'intérieur d'une application ASP.NET, l'option Application_Error sera appelé pour toutes les exceptions non gérées dans votre code, y compris celle de l'action de test que vous avez montrée. Ainsi, tout ce que vous avez à faire est de gérer cette exception dans l'événement Application_Error. Dans l'exemple de code que vous avez montré, vous ne traitez que les exceptions de type HttpException ce qui n'est évidemment pas le cas avec le Convert.ToInt32("a") code. Assurez-vous donc que vous enregistrez et traitez toutes les exceptions à cet endroit :

~~protected void Application_Error() { Exception unhandledException = Server.GetLastError(); HttpException httpException = unhandledException as HttpException; if (httpException == null) { Exception innerException = unhandledException.InnerException; httpException = innerException as HttpException; }

    if (httpException != null)
    {
        int httpCode = httpException.GetHttpCode();
        switch (httpCode)
        {
            case (int)HttpStatusCode.Unauthorized:
                Response.Redirect("/Http/Error401");
                break;

            // TODO: don't forget that here you have many other status codes to test 
            // and handle in addition to 401.
        }
        else
        {
            // It was not an HttpException. This will be executed for your test action.
            // Here you should log and handle this case. Use the unhandledException instance here
        }
    }
}~~ 

Le traitement des exceptions dans l'API Web peut se faire à différents niveaux. Voici une detailed article expliquant les différentes possibilités :

  • attribut de filtre d'exception personnalisé qui pourrait être enregistré comme un filtre d'exception global

    [AttributeUsage(AttributeTargets.All)]
    public class ExceptionHandlingAttribute : ExceptionFilterAttribute
    {
        public override void OnException(HttpActionExecutedContext context)
        {
            if (context.Exception is BusinessException)
            {
                throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
                {
                    Content = new StringContent(context.Exception.Message),
                    ReasonPhrase = "Exception"
                });
            }
    
            //Log Critical errors
            Debug.WriteLine(context.Exception);
    
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
            {
                Content = new StringContent("An error occurred, please try again or contact the administrator."),
                ReasonPhrase = "Critical Exception"
            });
        }
    }
  • invocateur d'action personnalisé

    public class MyApiControllerActionInvoker : ApiControllerActionInvoker
    {
        public override Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken)
        {
            var result = base.InvokeActionAsync(actionContext, cancellationToken);
    
            if (result.Exception != null && result.Exception.GetBaseException() != null)
            {
                var baseException = result.Exception.GetBaseException();
    
                if (baseException is BusinessException)
                {
                    return Task.Run<HttpResponseMessage>(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)
                    {
                        Content = new StringContent(baseException.Message),
                        ReasonPhrase = "Error"
    
                    });
                }
                else
                {
                    //Log critical error
                    Debug.WriteLine(baseException);
    
                    return Task.Run<HttpResponseMessage>(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)
                    {
                        Content = new StringContent(baseException.Message),
                        ReasonPhrase = "Critical Error"
                    });
                }
            }
    
            return result;
        }
    }

0 votes

J'aimerais que ce soit aussi simple, mais l'erreur n'est toujours pas détectée. J'ai mis à jour la question pour éviter toute confusion. Merci.

0 votes

@MatthewPatrickCashatt, si cette exception n'est pas prise en compte dans le système de gestion de l'information de l'entreprise. Application_Error cela signifie qu'un autre code l'a consommé avant. Par exemple, vous pouvez avoir des HandleErrorAttributes personnalisés, des modules personnalisés, ... Il existe des millions d'autres endroits où les exceptions peuvent être capturées et traitées. Mais le meilleur endroit pour le faire est l'événement Application_Error, car c'est là que toutes les exceptions non gérées vont se terminer.

0 votes

Merci encore, mais quoi qu'il en soit, le /test n'est pas atteint. J'ai placé un point d'arrêt sur la première ligne ( Exception unhandledException = . . . ) mais ne peut pas atteindre ce point d'arrêt dans l'application /test scénario. En revanche, si je mets une url bidon, le point d'arrêt est atteint.

8voto

Anders Points 7110

Pourquoi relancer, etc. Cela fonctionne et fera en sorte que le service renvoie le statut 500 etc.

public class LogExceptionFilter : ExceptionFilterAttribute
{
    private static readonly ILog log = LogManager.GetLogger(typeof (LogExceptionFilter));

    public override void OnException(HttpActionExecutedContext actionExecutedContext)
    {
        log.Error("Unhandeled Exception", actionExecutedContext.Exception);
        base.OnException(actionExecutedContext);
    }
}

2voto

COLD TOLD Points 8431

Avez-vous pensé à faire quelque chose comme un filtre d'action d'erreur de poignée comme

[HandleError]
public class BaseController : Controller {...}

vous pouvez également créer une version personnalisée de [HandleError] avec lequel vous pouvez écrire les informations d'erreur et tous les autres détails dans le journal.

0 votes

Merci, mais je l'ai déjà réglé globalement. Cela pose le même problème que ci-dessus, toutes les erreurs ne sont pas enregistrées.

1voto

Tim Points 202

Enveloppez le tout dans un try/catch et enregistrez l'exception non gérée, puis transmettez-la. À moins qu'il n'existe un meilleur moyen intégré de le faire.

Voici une référence Capture de toutes les exceptions (gérées ou non gérées)

(edit : oh API)

0 votes

Juste au cas où, il aurait besoin de rejeter l'exception aussi.

0 votes

@DigCamara Désolé, c'est ce que je voulais dire par "pass it on". throw ; devrait s'en occuper. J'ai d'abord dit "décider de quitter ou de recharger", puis j'ai réalisé qu'il avait dit que c'était une API. Dans ce cas, il est préférable de laisser l'application décider de ce qu'elle veut faire en la transmettant.

2 votes

Il s'agit d'une mauvaise réponse, car elle entraînera la duplication d'une grande quantité de code dans chaque action.

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