Accueil

Gestion des exceptions en MVC + ELMAH

by Jean-Camille Mercier 4. octobre 2012 09:18

Le BUG est l'ennemi du développeur par excellence avec sa forme la plus barbare la NotHandledException : "l'exception non gérée". Dans n'importe quel programme, un des premiers travail à effectuer est de mettre en place un système de récupération des exceptions de très bas niveau qui va avoir 3 rôles :

  1. Garder l'application en vie, au pire la redémarrer
  2. S'excuser auprès de l'utilisateur (c'est la moindre des choses)
  3. Avertir le développeur avec les informations nécessaires pour le debugging

Dans ce post, je vais m'intéresser à cette gestion d'exception en Asp.Net MVC car c'est un peu plus compliqué à mettre en place qu'un simple try {} catch {} dans le program.cs d'une application winForm par exemple.

1- Application_Error

Un site web MVC a pourtant bien un point d'entrée spécifique comme tout programme : c'est le Global.asax avec sa méthode Application_Start(). Donc logiquement, le point le plus bas pour trapper les exceptions du site se situe ici ! Pour cela il faut ajouter la méthode correspondante Application_Error() qui est appelée à chaque fois qu'une exception non gérée se produit. Celle-ci est alors accessible avec Server.GetLastError() :

protected void Application_Error(object sender, EventArgs e)
{
  Exception exception = Server.GetLastError();
  // TODO : log & mail
}

Le désavantage de cette méthode réside dans sa force : elle est bas niveau mais aussi TROP bas niveau, car lorsque vous êtes ici, le moteur MVC n'est plus actif, vous ne pouvez donc plus interagir avec les controllers et les vues ce qui est souvent nécessaire. Elle n'en reste pas moins obligatoire pour toutes les autres erreurs.

2- Exception Filter : HandleErrorAttribute

Il faut donc adjoindre une gestion complémentaire des exceptions par des filtres qui se placent sur les controllers ou sur les actions (cf ci-dessous). Le gestionnaire de base se nomme HandleErrorAttribute, il a pour but de rediriger l'utilisateur vers la vue partagée "Shared\Error" lorsqu'une exception se présente. C'est sur celle-ci qu'il est donc approprié de s'excuser de la gène occasionnée auprès de l'utilisateur.

[HandleError]
public class HomeController : Controller
{
  [HandleError]
  public ActionResult Index()
  {
    ViewBag.Message = "Home.";
    return View();
  }
}

Etant donné que pour être complet il faudrait placer à la main l'attribut sur toutes actions et que ce travail est beaucoup trop fastidieux, il est préférable de les ajouter de manière automatique à toute l'application avec la méthode RegisterGlobalFilters(). C'est d'ailleurs le choix qui a été fait dans le template par défaut de MVC4 dans le fichier App_Start\FilterConfig.cs

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
  filters.Add(new HandleErrorAttribute());
}

La vue Shared\Error est fortement typée sur l'objet System.Web.Mvc.HandleErrorInfo où l'on trouve toutes les informations nécessaires à l'erreur (message, stack trace, controler, action, etc ...) il est donc possible de les afficher ici, même si l'utilisateur final sera sûrement choqué à la vue de ces informations. C'est pourquoi il est préférable de mettre plutôt un système de log et d'envoi de mail au développeur pour l'informer du bug. Il faut donc créer votre propre filtre en dérivant HandleError et en surchargeant sa méthode OnException() :

public class LogErrorAttribute : HandleErrorAttribute
{
  public override void OnException(ExceptionContext filterContext)
  {
    // TODO : log & mail
    base.OnException(filterContext);
  }
}

Nous avons donc bien maintenant nos 3 actions : application up, excuses utilisateur sur la page, information au développeur

3- ELMAH

Je parlais dans un billet précédent d'avoir un framework perso pour pérenniser les développements, cela fait donc plusieurs années que j'ai développé une classe pour faire des Traces classiques dans un fichier de log et dans le journal d'évènements windows. Elle fonctionne bien que ce soit en Winform, web, silverlight, service, etc ... et je l'utilise maintenant par réflexe. Cependant, mes recherches pour écrire ce billet m'ont fait découvrir ELMAH qui est un framework de log dédié à MVC et qui j'avoue ne manque pas d'atouts ! Pour commencer, l'installation est très simple grâce à NuGet :

PM> Install-Package elmah

Votre Web.config va être automatiquement paramétré avec les handlers nécessaires ainsi que la route pour accéder à la page de visualisation des exceptions :

Alors bien-sûr cette page ne doit pas être accessible à tout le monde car les informations qu'elle contient sont plutôt sensibles et un hacker pourrait les exploiter à votre insu ! Il faut donc soit la rendre inaccessible de l'extérieur avec <security allowRemoteAccess="0" />, soit la protéger en n'autorisant, par exemple, que les administrateurs du site d'y avoir accès en se basant sur les rôles d'Asp.net (cf mon billet sur l'authentification MVC4) :

<location path="elmah">
  <system.web>
    <authorization>
      <allow roles="admin" />
      <deny users="*" />  
    </authorization>  
  </system.web>
</location>

Mais une page d'affichage n'est pas suffisante, il faut aussi prévenir les développeurs et heureusement Elmah propose un fonctionnement "Out of the box" pour envoyer un mail à chaque bug, il suffit juste d'ajouter (et remplir correctement) la section ci-dessous dans le fichier de config :

<elmah>
  <errorMail
      from="elmah@example.com"
      to="admin@example.com"
      subject="Erreur sur le site ..."
      priority="Low|Normal|High"
      async="true|false"
      smtpPort="25"
      smtpServer="smtp.example.com"
      useSsl="true|false"
      userName="johndoe"
      password="secret"
      noYsod="true|false" />
</elmah>

La dernière chose qu'il me semble utile de souligner est qu'il est possible de modifier la persistance des exceptions en allant les enregistrer dans une base de données par exemple. Pour SQL serveur il faut donc commencer par créer une nouvelle table et quelques procédures stockées. Le fichier script est disponible dans le dossier "App_readme" ou sur le site de l'auteur : http://code.google.com/p/elmah/downloads/detail?name=ELMAH-1.2-db-SQLServer.sql

Ensuite il faut juste indiquer la chaîne de connexion de votre BDD à Elmah toujours dans le fichier de config : 

<elmah>
  <errorLog type="Elmah.SqlErrorLog, Elmah" 
            connectionStringName="MyConnection" />
</elmah>

 

 Conlusion

Négliger la gestion des exceptions dans une application est toujours un mauvais calcul car tôt ou tard vous aurez besoin de savoir ce qu'il s'est passé, comment, par qui et pourquoi. Toutes ces informations ne sont pas très évidentes à obtenir en MVC et je trouve que ELMAH est une solution complète et efficace pour cette problématique.

Sources

http://www.codeproject.com/Articles/422572/Exception-Handling-in-ASP-NET-MVC