Sunday, November 8, 2009

Windsor-managed HttpModules

Another interesting question from stackoverflow:

I have a custom HTTP Module. I would like to inject the logger using my IoC framework, so I can log errors in the module. However, of course I don't get a constructor, so can't inject it into that. What's the best way to go about this?

First thing we need to recognize is that there are actually two problems here:

  1. Taking control of IHttpModule instantiation in order to inject it services, and
  2. Managing the lifecycle of the IHttpModule

Let's start with the lifecycle. Ayende has the best summary of the lifecycle of HttpApplication and its related IHttpModules I've found. If you're not familiar with this, go read it now, I'll wait.

Back so soon? Ok, we can implement this in Windsor with a pluggable lifestyle. Lifestyle managers dictate when it's necessary to create a new instance of the component, but not how. They don't get to actually create the instance, that's the responsibility of the component activator. Here's the full component creation flow reference.

So we need to write a new lifestyle manager that allows at most one component instance per HttpApplication instance. I won't bother you with the implementation details since it's very similar to the per-request lifestyle: it's composed of the LifestyleManager itself and a HttpModule as a helper to store component instances.

Now we need a way to manage the IHttpModule instantiation. Unsurprisingly, we can do that with another IHttpModule, which I'll call WindsorHttpModule.
This WindsorHttpModule will be responsible for the initialization of the "user-level", Windsor-managed IHttpModules.

So, to summarize:

  1. Register PerHttpApplicationLifestyleModule and WindsorHttpModule:
    <httpModules>
        <add name="PerHttpApplicationLifestyleModule" type="HttpModuleInjection.PerHttpApplicationLifestyleModule, HttpModuleInjection"/>
        <add name="WindsorModule" type="HttpModuleInjection.WindsorHttpModule, HttpModuleInjection"/>
    </httpModules>
  2. Write your http modules using normal dependency injection style, e.g.:
    public class Service {
        public DateTime Now {
            get { return DateTime.Now; }
        }
    }
    
    public class UserHttpModule : IHttpModule {
        private readonly Service s;
    
        public UserHttpModule(Service s) {
            this.s = s;
        }
    
        public void Init(HttpApplication context) {
            context.BeginRequest += context_BeginRequest;
        }
    
        private void context_BeginRequest(object sender, EventArgs e) {
            var app = (HttpApplication) sender;
            app.Response.Write(s.Now);
        }
    
        public void Dispose() {}
    }
  3. Make your HttpApplication implement IContainerAccessor
  4. Register your http modules in Windsor, using the custom lifestyle:
    container.AddComponent<Service>();
    container.Register(Component.For<IHttpModule>()
                           .ImplementedBy<UserHttpModule>()
                           .LifeStyle.Custom<PerHttpApplicationLifestyleManager>());

    Note that UserHttpModule is not registered in the <httpModules> section of web.config, since it's managed by Windsor now.

You can also combine this with Rashid's BaseHttpModule to make your modules more testable.

Full source code is here.

4 comments:

stuart said...

Hi Mauricio. Thanks for the great post. It worked like a charm for me and is exactly what i wanted. However, I am now trying to extend my custom HTTP module in order to run a background service similar to this example. My background service takes the feed and attempts to save the data to a database. I am injecting my database service as per your example. When I launch the site, the background service runs fine the first time (ie. the feed data is saved to the database) but then bombs out the second time it tries to run. More accurately, I get a System.ObjectDisposedException on my DoWork method when it hits the database service code. Is it possible to get the database service instance to persist across these kind of timer callbacks or should i be trying a different method to DI? I have also tried to use a Common Service Locator but no joy there either. I hope this makes sense to you and that I have explained it properly. I would be grateful for any help you might be able to offer.

stuart said...

Hello again. I have my common service locator code working now - the problem with the csl was that one of my windsor components involved in providing the database service was using a perwebrequest lifestyle and the timer callback method runs outside asp.net. I changed it over to transient and it works fine. Thanks for the original article though. I can still use that in other part of my app.

mausch said...

Hi Stuart, yes it looks like a lifestyle issue. The HttpModule lifestyle introduced here is "bigger" than a per-request lifestyle so a second request would still have that service instance from the first request. In your case you could just register your HttpModule with a per-request or transient lifestyle.
Anyway I recommend taking a look at Quartz.Net for task scheduling

Halloween Fancy Dress said...

Cheers for the help, really useful!