If you've been developing Asp.net web applications you've probably come across the IHttpModule
interface, that makes it possible to write reusable request-level event handlers. The same thing can of course be done by writing event handlers inside the Global.asax
codebehind, but then you wouldn't be able to reuse the same code unless you do a copy/paste.
But Global.asax
codebehind has one particular advantage over your custom modules: it can also attach to application-level events like application start event for instance. As per documentation, this is not possible with a custom module. Or so I thought...
There's a sequel to this blog post and I advise you to read it right after you finish this one. Code provided here may not always work as expected. Find out why. How to correctly useIHttpModule
to handleApplication_OnStart
event.
The problem I had to solve was to extend an existing web application (no source code) and add route mapping to it. This is normally done within an application start event, but that one is handled by the Global.asax
codebehind.
The most obvious possibility was to write a new class that inherits from the HTTP application of the existing web application (let's call it SPHttpApplication
- those of you who have been developing applications for Sharepoint probably know this class very well) and add an additional event handler that would handle application start event. After we've written this class we'd have to change Global.asax
file itself to change something like this:
<%@ Application Inherits="Microsoft.Sharepoint.ApplicationRuntime.SPHttpApplication" ... >
into this:
<%@ Application Inherits="Reusables.RoutedHttpApplication" ... >
and that's it.
But I didn't want to change this and mangle with existing application's classes and definitions. Usually this means that some months/years later nobody will ever notice this change and when an application will be upgraded/moved, routing will stop working. On the other hand, when you upgrade/move an existing application you usually check web.config
settings and apply them on the new instance.
IHttpModule to the rescue
So I took the road less travelled and started exploring .net code by using excellent .Net Reflector tool. The biggest question bothering me was: When and how many times does a custom HTTP module get instantiated? Because I could use this knowledge to maybe hook into application start event.
What I found out (as I anticipated) is that all configured HTTP modules get instantiated only once per application life. At it's beginning or in other words at application start. This was a great confirmation that could solve my problems altogether.
When we write a custom HTTP module we implement IHttpModule
interface that looks like this:
1: public interface IHttpModule
2: {
3: void Dispose();
4: void Init(HttpApplication context);
5: }
Init()
method. Translate this into a more readable language:
HTTP module's Init()
method can be thought of an application start event handler.
But instead of casting our usual event's parameter object sender
to HttpApplication
, this method's parameter is already of an appropriate type. Great!
As mentioned earlier in this post I had to register MVC functionality (areas, routes and other minor customizations) in an existing Sharepoint site, so I've simply written this class up:
1: public class MvcRegistratorModule : IHttpModule
2: {
3: /// <summary>
4: /// Disposes of the resources (other than memory) used by the module that implements <see cref="T:System.Web.IHttpModule"/>.
5: /// </summary>
6: public void Dispose()
7: {
8: // dispose any resources if needed
9: }
10:
11: /// <summary>
12: /// Inits the specified application.
13: /// </summary>
14: /// <param name="application">The application.</param>
15: public void Init(HttpApplication application)
16: {
17: AreaRegistration.RegisterAllAreas();
18: this.RegisterRoutes(RouteTable.Routes);
19: }
20:
21: /// <summary>
22: /// Registers application routes.
23: /// </summary>
24: /// <param name="routes">Routes collection to which we'd like to assign new routes.</param>
25: private void RegisterRoutes(RouteCollection routes)
26: {
27: // clear routes first just in case this method gets called multiple times during web application's lifetime
28: routes.Clear();
29:
30: routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
31:
32: // default application route
33: routes.MapRoute(
34: "Default",
35: "{controller}/{action}/{identifier}",
36: new { controller = "Home", action = "Index", identifier = UrlParameter.Optional }
37: );
38: }
39: }
After this was done I had to put my custom MvcRegistratorModule
in a web.config
file and voila. Asp.net MVC initialization's done. Worked like a charm.
As it turns out some modules may incorrectly work when they modify common application-wide shared resources. Read sequel to this post How to correctly useIHttpModule
to handleApplication_OnStart
event to find out more information that will help you write modules correctly.
Possible reusability improvements
I could pack this class (and others that will be reused as well) in a separate assembly, strong sign it and provide some other means of route configuration than hard coded as they are now. They could be defined in:
- a
web.config
file - I'd also write my ownConfigurationSection
as well asConfigurationElement
classes to make this standardized - a database - this would also need some additional classes
- whereever else which is application specific
To make this even more flexible I could have a configuration that would actually tell, where routing is defined. It could be a class that implements a certain interface or is a child of a certain abstract base class. This would also give me a nice possibility to write additional route mapping configuration providers if needed.
But I'll leave this to you dear reader. The main idea here has been achieved: Custom HTTP modules can reliably handle application start event. It's not directly application start event, but it does run only once per application life. At its start.
Excellent tip!
ReplyDelete@Anonymous: Glad you liked it. Make sure you read the second part of this blog as well, so you don't get any unexpected behaviour when you write your own HTTP module.
ReplyDeleteGood work, I appreciate.
ReplyDelete