Harnessing your IoC Container to perform application event tasks in ASP.NET MVC

Posted on Monday, 29 Dec 2014

I've always found software architecture a fascinating subject, and one area I'm particularly interested in at the moment is dependency management.

Recently I've been reading a lot about IoC patterns and best practices, and being a subscriber to PluralSight.com I stumbled across a fantastic course by Matt Honeycutt titled "Build Your Own Application Framework with ASP.NET MVC 5". The video course presented a whole array of work that can be done to increase productivity within ASP.NET MVC, and one part of the course that particularly stuck out was the use of start-up tasks.

As an aside, I would highly recommend checking out Matt Honeycutt's course if you've got an active subscription. For those who do not have a PluralSight subscription then PluralSight offer a free trial so you can sign up and watch the course for free. It's 3 hours 25 minutes in length and packed full of tips for modifications, functionality and extensions that can make you more productive in ASP.NET MVC.

The problem

As an ASP.NET MVC application grows so does the use of the application event methods accessible from Global.asax.cs such as Application_Start, Application_BeginRequest, Application_EndRequest and so on. After a while these methods can become unmanageable and messy as more and more functionality is tied into the application events.

Let me demonstrate with an example. Take this simple Application_start method:

protected void Application_Start()
{
     IContainer container = StructureMapCoreSetup.Initialise();
     container.Configure(c => c.IncludeRegistry<DefaultRegistry>());

     StructureMapResolver = new StructureMapDependencyResolver(container);
     DependencyResolver.SetResolver(StructureMapResolver);

     ViewEngines.Engines.Clear();
     ViewEngines.Engines.Add(new RazorViewEngine());

     AreaRegistration.RegisterAllAreas();

     FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
     RouteConfig.RegisterRoutes(RouteTable.Routes);
     BundleConfig.RegisterBundles(BundleTable.Bundles);
}

If you look closely you'll see that this method is responsible for the following actions:

  1. Setup our IoC container (in this case it's StructureMap)
  2. Register it with ASP.NET MVC's dependency resolver
  3. Clear existing view engines and explicitly add Razor
  4. Register all areas of the application
  5. Register any global filters
  6. Register the application routes
  7. Finally register the application's Javascript and CSS bundles

Even with these reasonably tidy 7 steps, you can see how the method has the potential to become quite convoluted.

The solution

One such way to keep these application event methods in shape and prevent your global.asax.cs class becoming a god object  is to harness the power of your IoC container and break the functionality down into modular tasks. These tasks can then by loaded by your IoC container and executed at the correct stage of the life-cycle of a page request. Let's take a look at how we can do this.

First we're going to use a simple implementation of the command pattern to define our task execution interfaces:

public interface IExecutable
{
    void Execute();
}

Now we've created our role interface we also need to create an interface for the various stages of the application's life-cycle that we wish to fire off tasks during.

public interface IRunAtStartup : IExecutable
{
}

public interface IRunOnEachRequest : IExecutable
{
}

public interface IRunAtEndOfEachRequest : IExecutable
{
}

From here we can begin to categorise our methods into nice, well organised classes based off of the interface they implement.

For instance, I often move all of the basic framework set-up tasks into their own FrameworkTasks.cs  class like so:

public class FrameworkSetupTask : IRunAtStartup
{
    public void Execute()
    {
        AreaRegistration.RegisterAllAreas();

        ViewEngines.Engines.Clear();
        ViewEngines.Engines.Add(new RazorViewEngine());

        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
}

In addition to this if my project has various AutoMapper profiles then I'll move them into their own start-up task class that's fire off when the application is run:

public class AutoMapperProfiles : IRunAtStartup
{
    public void Execute()
    {
        Mapper.AddProfile(new CoreAutoMapperProfile());
        Mapper.AddProfile(new CartAutoMapperProfile());
        Mapper.AddProfile(new ProductAutoMapperProfile());
    }
}

Setting up your IoC container to execute the tasks

Now we've abstracted our various application life-cycle tasks we can use our IoC container to load and execute the tasks based on their interface type.

In the following example you'll notice that I'm using StructureMap as my IoC container. You may also notice that I'm favouring the nested container per HTTP request approach - this is a personal choice of mine so it's possible that your setup may vary. Other than this difference there shouldn't be much to change in the following examples.

The Task Runner method
The task runner is the method responsible for loading and executing the tasks accordingly. You'll notice the argument to the TaskRunner method is a nullable container. This is so I can pass a (nested) container as an argument (see Application_Start as an example), otherwise the TaskRunner method will use an existing instance of the nested container that's set up within my StructureMapDependencyResolver  class.

public class MvcApplication : HttpApplication
{
    public static StructureMapDependencyResolver StructureMapResolver { get; set; }

    protected void Application_Start
    {
        ...

        StructureMapResolver = new StructureMapDependencyResolver(container);

        var serviceLocatorProvider = new ServiceLocatorProvider(() => StructureMapResolver);
        container.Configure(cfg => cfg.For<ServiceLocatorProvider>().Use(serviceLocatorProvider));

        DependencyResolver.SetResolver(StructureMapResolver);

        TaskRunner<IRunAtStartup>(container.GetNestedContainer());
    }

    protected void Application_BeginRequest(object sender, EventArgs e)
    {
        StructureMapResolver.CreateNestedContainer();

        TaskRunner<IRunOnEachRequest>();
    }

    protected void Application_EndRequest(object sender, EventArgs e)
    {
        TaskRunner<IRunAtEndOfEachRequest>();
    }

    private static void TaskRunner<T>(IContainer container = null)
    {
        if (container == null)
        {
            container = StructureMapResolver.CurrentNestedContainer;
        }

        foreach (var taskInstance in container.GetAllInstances<T>())
        {
            var task = taskInstance as IExecutable;
            if (task != null)
            {
                task.Execute();
            }
        }
    }
}

As you can see our HttpApplication events are far cleaner now than they were before and will continue to remain manageable as the project grows and further functionality is added that requires hooking up to the HttpApplication events.

Conclusion

As demonstrated, creating event tasks is straight forward and can easily be set up with any IoC container. If you're using StructureMap and instead opting to use an IoC Container such Ninject or Unity then it should be easy enough to create a similar TaskRunner  method.

You'll notice that for this example I've set up event tasks for the main HTTP events (Application_Start , Application_BeginRequest and Application_EndRequest) but this pattern can easily be applied to some of the other HttpApplication events such as Application_Error.

I've used this approach in various ASP.NET MVC projects with great success. The only drawback I've been aware of is the fact that you lose the ability to instantly see what occurs during your application events as they've now been moved into their own classes, though I feel this is a minor setback for the tidiness of the approach.

As mentioned earlier in my post, I would definitely recommend checking out the PluralSight course if you can.

Back