15 July 2010 - 10:30 PM / by Dominic Pettifer. 2 Comments for Put an IOC Powered Bootstrapper in your ASP.NET MVC Application.
Technical Article - Do you have a fat Global.asax file in your ASP.NET MVC application? Does it contain 1000’s of lines of application start-up code? Want to break it out into separate classes and gain the benefits of decoupling, dependency injection and unit testability? Read on.
In your ASP.NET MVC app you’ll find the Global.asax file. This normally contains your route registration code, but over time this file gets clogged up with all sorts of various application initialisation (start-up) routines such as hooking up IOC Containers, initialising alternative view engines (Spark), very large and complicated routing logic, registering Areas, starting up background tasks, registering Validation handlers, Model Binders, and many more.
If you’re trying to adhere to the SoC (Separation of Concerns) principle, you don’t want all this start-up code sitting inside a single class. You’ll want to break it up into separate classes. Maybe you want some of this start-up code to be unit testable and also take advantage of Dependency Injection.
With the Bootstrapper pattern you can reduce your Global.asax code down to just this:
using MyLibrary.Bootstrapper;
namespace MyWebApp
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
Bootstrapper.Run();
}
}
}Before we dive into how the Bootstrapper class works, we need to take note that this example relies on using an IOC (Inversion Of Control) Container. Specifically we’re using Castle Windsor for our IOC needs. I’ve already written an article on Dependency Injection with Castle Windsor and the benefits of this approach, as well as how to hook Castle Windsor up to your MVC application, so please consult that article. But briefly, an IOC Container lets you decouple your codebase by preventing modules depending on each other, but instead making them depending on interfaces and abstractions instead, this has code maintainability and unit testability benefits.
Let’s take a look at the Bootstrapper class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Castle.Core;
using MyLibrary.Ioc;
namespace MyLibrary.Bootstrapper
{
public class Bootstrapper
{
static Bootstrapper()
{
Type bootStrapperType = typeof(IBootstrapperTask);
IList<Assembly> assemblies = AppDomain.CurrentDomain
.GetAssemblies().Where(a => !a.GlobalAssemblyCache).ToList();
List<Type> tasks = new List<Type>();
foreach (Assembly assembly in assemblies)
{
var types = from t in assembly.GetTypes()
where bootStrapperType.IsAssignableFrom(t)
&& t.IsInterface == false
&& t.IsAbstract == false
select t;
tasks.AddRange(types);
}
foreach (Type task in tasks)
{
if (!IocHelper.Container().Kernel.HasComponent(task.FullName))
{
IocHelper.Container().AddComponentLifeStyle(
task.FullName, task, LifestyleType.Transient);
}
}
}
public static void Run()
{
Type priorityType = typeof(BootstrapperPriorityAttribute);
IList<IBootstrapperTask> tasks = IocHelper
.Container().ResolveAll<IBootstrapperTask>()
.OrderBy(t =>
{
Type taskType = t.GetType();
BootstrapperPriorityAttribute priority = taskType
.GetCustomAttributes(priorityType, false)
.SingleOrDefault() as BootstrapperPriorityAttribute;
if (priority != null)
{
return priority.Priority;
}
return Int32.MaxValue;
})
.ToList();
foreach (IBootstrapperTask task in tasks)
{
task.Execute();
}
}
}
}In the static constructor we’re using Reflection to look through all loaded Assemblies (that aren’t in the GAC) to find any types that implement the IBootstrapperTask interface, it’s these IBootstrapperTask classes that actually contain the start-up code for our application. We then register each type with our IOC Container, checking that it has not already been registered. See my previous article on IOC Containers to see how the IocHelper class is implemented and how to hook it up.
In the Run() method we simply loop through each registered IBootstrapperTask and call its Execute() method. Because these IBootstrapperTask types are being resolved by our IOC Container, any dependencies they may have are resolved automatically, as we’ll see later. We’ll come back to the OrderBy LINQ expression later.
The IBootstrapperTask interface is very simple:
namespace MyLibrary.Bootstrapper
{
public interface IBootstrapperTask
{
void Execute();
}
}And an implementation for registering Routes:
using System.Web.Mvc;
using System.Web.Routing;
using MyLibrary.Bootstrapper;
namespace MyWebApp.StartUpTasks
{
public class RegisterRoutes : IBootstrapperTask
{
private RouteCollection Routes = null;
public RegisterRoutes() : this(RouteTable.Routes) { }
public RegisterRoutes(RouteCollection routes)
{
this.Routes = routes;
}
public void Execute()
{
Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
Routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
}
}You don’t need to do anything to register this Bootstrapper Task, the Bootstrapper will find it automatically. We have two constructors, one lets us pass in a RouteCollection so we can unit test this start-up task.
What if we want to pass in dependencies such as needing to access a database in our Bootstrapper Task? The following is an example of a background process that trawls Flickr for photos and adds them to a database, the Flickr API and Database layer are abstracted away:
public class FlickrPhotoTrawler : IBootstrapperTask
{
protected readonly IPhotoRepository PhotoRepository = null;
protected readonly IPhotoHostingService PhotoHostingService = null;
public PhotoTrawler(IPhotoRepository photoRepository, IPhotoHostingService photoHostingService)
{
this.PhotoRepository = photoRepository;
this.PhotoHostingService = photoHostingService;
}
public void Execute()
{
Timer timer = new Timer(1);
timer.Elapsed += new ElapsedEventHandler(Timer_Elapsed);
timer.AutoReset = false;
timer.Start();
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
Timer timer = (Timer)sender;
IList<HostedPhoto> photos = PhotoHostingService.GetLatestPhotos();
foreach (HostedPhoto hostedPhoto in photos)
{
// Process each photo, and add it a database via the PhotoRepository
}
timer.Interval = 10 * 1000;
timer.Start();
}
}We register IPhotoRepository and IPhotoHostingService with the Castle Windsor in the normal way. Because we’re injecting interfaces into our Bootstrapper task, we can easily unit test it (well maybe the Timer will give us trouble, but you get the idea).
If we want to control the order in which tasks are executed, we simply add a BootstrapperPriorityAttribute to our Bootstrapper classes:
[BootstrapperPriority(Priority = 1)]
public class RegisterRoutes : IBootstrapperTask
{
//... implementation (snip)... //
}And the implementation for BootstrapperPriorityAttribute:
using System;
namespace MyLibrary.Bootstrapper
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class BootstrapperPriorityAttribute : Attribute
{
private int _priority = 0;
public BootstrapperPriorityAttribute() { }
public BootstrapperPriorityAttribute(int priority)
{
_priority = priority;
}
public int Priority
{
get { return _priority; }
set { _priority = value; }
}
}
}This is used in the OrderBy LINQ expression in the Run() method in the Bootstrapper class (see code above).
If you want to support dropping DLLs into an existing web application to add additional functionality, you can use the Bootstrapper in an IHttpModule like so:
using System;
using System.Web;
namespace MyWebApp.Modules
{
public class WidgetFrameworkInitialisation : IHttpModule
{
private static volatile bool HasAppStarted = false;
private readonly static object _syncObject = new object();
public void Init(HttpApplication context)
{
if (!HasAppStarted)
{
lock (_syncObject)
{
if (!HasAppStarted)
{
Bootstrapper.Run();
HasAppStarted = true;
}
}
}
}
public void Dispose() { }
}
}This may look strange, but multiple HttpApplication instances are created during an ASP.NET app’s lifetime, these deal with multiple incoming requests and are sent back into a pool (similar to database connection pooling). For this reason the HttpModule’s Init() method is called multiple times, not just once. So we need a way to ensure the Bootstrapper’s Run method is only called once, we use the Double-checked Locking pattern for this.
You can use this code in an ASP.NET Webforms application too.
You may be thinking that you could pass in the instance of the HttpApplication from the Init() method of your HttpModule, this would be available via the Global.asax too in an MVC application eg:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
Bootstrapper.Run(this);
}
}This would be useful for setting up HttpApplication events such as BeginRequest, EndRequest etc. inside a Bootstrapper Task, eg:
public class HookUpEvents : IBootstrapperTask
{
public void Execute(HttpApplication context)
{
context.BeginRequest += new EventHandler(BeginRequest);
}
private void BeginRequest(object sender, EventArgs e)
{
// Begin request code (snip)
}
}But you must avoid doing this at all costs. Though the Application_Start() method is guaranteed to run just once, and similarly with the HttpModule code above, because multiple HttpApplication instances are used, you’ll only be passing in one instance of potentially many, and subsequently hooking up BeginRequest, EndRequests etc. events to only the first HttpApplication instance. The effect being that sometimes your events will fire, sometimes they won’t, depending on which HttpApplication instance is serving the current request, and whether it was lucky enough to be the first to get instantiated and get its events hooked up via a Bootstrapper Task.
This is a shame as it would be nice to move the hooking up of HttpApplication events into a Bootstrapper task. I’m not sure of a solution for this, I’ll leave this as an exercise to the reader.
Why are you hacking reflection instead of using WIndsor's registration API?
Posted on 21 July 2010 - 7:02 AM / by Krzysztof Kozmic
Good question. I wanted to keep the Reflection/Registration code reasonably IOC Container agnostic, because you can apply these ideas to other IOC Containers as well, not just Castle Windsor.
But you're right, you could use Windsor's fluent Registration API
Posted on 30 July 2010 - 12:43 AM / by Dominic Pettifer (Administrator)
@gafroninja Does your hair stand up for 10 mins each day, then you have a Scrum haircut
1:37 PM September 3rd from Echofon@scottgal Teeth are nature's nail clippers :-)
10:42 AM September 3rd from Echofon@brucel Those are amazing! Does the 6th one down (psychedelic colours) mean we can create gradient effects in CSS3? #css3
4:28 PM September 2nd from EchofonSorry for the rant, but if you tweet YouTube videos hidden behind URL shortening services, please warn beforehand
2:01 PM September 2nd from EchofonWHOEVER THOUGHT AUTO PLAYING VIDEOS WERE A GOOD IDEA SHOULD BE SHOT!!!!!!!1!!!!11!1!!
1:57 PM September 2nd from Echofon