vendredi 12 mars 2010 00:04
tja
[ASP.NET] Ne pas se faire avoir par IHttpModule et sa méthode Init()
Beaucoup de développeurs pensent que lorsqu’on créé et enregistre un IHttpModule, il n’en existe qu’une seule instance et la méthode Init() sera appelée qu’une fois.
C’est faux
Cela peut vraiment créer des bugs subtils dont en ne se rend pas compte tout de suite. J’ai eu dernièrement le cas sur un projet ASP.NET MVC sur lequel je travaille mais c’est valable également pour ASP.NET web forms classiques. C’est normal, c’est la même plateforme et les IHttpModules marchent de la même manière.
Mon module IHttpModule
HttpModules sont très utiles lorsque vous voulez vous brancher sur différents évènement émis par ASP.NET comme par exemple Begin_Request ou End_Request. Cela permet d’injecter dynamiquement de la fonctionnalité dans l’application ASP.NET. Un des modules les plus connus dans le monde MVC est par exemple UrlRouting.
Pour ma part j’ai crée un HttpModule pour pouvoir enregistrer et configurer mon containeur IoC StructureMap. Mon HttpModule est en quelque sort le CompositionRoot de mon application. Il se présente comme ceci :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class StructureMapDependencyRegistrarModule : IHttpModule {
protected static bool DependenciesRegistered;
public void Init(HttpApplication context) { context.BeginRequest += ContextBeginRequest; }
private void ContextBeginRequest(object sender, EventArgs e) { EnsureDependenciesRegistered(); }
protected internal override void EnsureDependenciesRegistered() { // Simply clears default services injected into the container after initialization. // This is mostly used to clear out any mock or stub objects introduced in a unit test after the unit test is over. ObjectFactory.ResetDefaults(); DependencyRegistrar = new StructureMapDependencyRegistrar(); DependencyRegistrar.ConfigureOnStartup(); DependenciesRegistered = true; } }
|
Le module est enregistré de cette manière dans Web.config :
1 2 3 |
<httpModules> <add name="DependencyRegistrar" type="Jaskula.DependencyResolution.StructureMap.StructureMapDependencyRegistrarModule, Jaskula.DependencyResolution"/> </httpModules> |
Cela peut faire penser qu’une seule instance de IHttpModule sera créée et que la méthode Init() sera appelée qu’une fois et ainsi notre code d’initialisation sera exécuté qu’une seule fois lorsque la requête Http arrive. Cependant dans la vraie vie cela ne se passe pas comme ça. La méthode Init()sera exécutée plusieurs fois. Pour expliquer ce phénomène il faut comprendre le fonctionnement de HttpApplication contexte.
HttpApplication
HttpApplication contexte c’est l’objet qui est passé dans votre méthode Init() et qui est utilisé pour traiter une seule requête Http. Quand votre application démarre, le ASP.NET worker process instanciera autant d’objets HttpApplication qu’il estime avoir besoin et les insère dans son pool d’applications. Cela est fait pour optimiser les performances en réutilisant les instances déjà créées et le renvoyer au pool une fois utilisées. La création de ses objet est généralement très chère d’où l’idée de pooling comme dans le cas des connections à une base de données par exemple.
Lorsqu’une requête Http arrive, l’objet HttpApplication instanciera une copie de chaque IHttpModule enregistré et appellera sa méthode Init(). Donc si dans le pool il y a 10 objets HttpApplication vous allez avoir 10 copies de votre IHttpModule et sa méthode Init() sera appelée 10 fois ! Le nombre d’objets HttpApplication créés dans le pool dépendra du processus ASP.NET et son estimation de l’optimisation nécessaire. Donc si par exemple votre module utilise beaucoup de ressources externes il est possible qu’il soit créé plus de copies dans le pool.
Qu’il s’exécute qu’une seule fois !
Pour qu’il s’exécute qu’une seule fois nous allons recourir au vieux pattern double check/lock strategy ce qui nous assurera que la méthode Init() soit appelée qu’une seule fois. Notre module ressemble maintenant à ça :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
public class StructureMapDependencyRegistrarModule : IHttpModule {
protected static bool DependenciesRegistered; protected static readonly object Lock = new object();
public void Init(HttpApplication context) { context.BeginRequest += ContextBeginRequest; }
private void ContextBeginRequest(object sender, EventArgs e) { EnsureDependenciesRegistered(); }
protected internal override void EnsureDependenciesRegistered() { if (!DependenciesRegistered) { lock (Lock) { if (!DependenciesRegistered) { // Simply clears default services injected into the container after initialization. // This is mostly used to clear out any mock or stub objects introduced in a unit test after the unit test is over. ObjectFactory.ResetDefaults(); DependencyRegistrar = new StructureMapDependencyRegistrar(); DependencyRegistrar.ConfigureOnStartup(); DependenciesRegistered = true; } } } } }
|
Comme vous le voyez, il faut être très vigilent car ceci peut produire des bugs très subtils. Dans mon cas le code aurait enregistré plusieurs fois les mêmes types dans le containeur IoC ce qui finirai par jeter une exception. Ceci est un hint que je vous donne lorsque vous serez amené à jouer avec les modules http.
Bon codage :)
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :