MVC3 and MEF

ASP.NET MVC has been steadily maturing into a first rate web application framework. New features have been progressively enhancing the framework, changing how we how build more robust, flexible and effective applications. With the third iteration of the framework (currently in Beta), the MVC team have changed the underlying architecture to support service location, a much desired addition, allowing us to greatly decouple our implementations away from the framework.

MVC3 with the Managed Extensiblity Framework
Previously I’ve explored how to integrate the Managed Extensibility Framework (MEF) with MVC (version 2), and to some success, although this was mostly limited to Controllers and a rethink of the routing registration system. There was still a little too much wire up for my liking too.

So now with a fresh look at how MVC3 is changing the framework, we can see that MEF can’t completely be the center of our IoC/SL universe in this project. This revised project simplifies a lot of the wire up of our component parts, thanks to MEF’s composition engine, and allows use to handle the resolution of automatically discovered controllers, filters, etc., essentially any of the extension points in MVC3 can be used with exports via MEF.

So, how do we do this? The key is MVC3′s new IDependencyResolver interface. Much like its ancestral root, the Common Service Locator (http://commonservicelocator.codeplex.com), the IDependencyResolver interface is simply an abstraction over service location. The important part here, is that in MVC3, the IDependencyResolver is used to attempt to resolve parts before using the configured defaults. What the team has provided though, is single extension point that makes it all possible, plus they haven’t forced us to use any particular IoC Container, or Service Locator, we are free to use our own.

What we need to do first, is create an IDependencyResolver that uses our MEF container.

/// /// Resolves types using the Managed Extensibility Framework. ///
public class MEFDependencyResolver : IDependencyResolver
{
#region Fields
private readonly CompositionContainer container;
#endregion

#region Constructor /// <summary> /// Initialises a new instance of <see cref="MEFDependencyResolver"/>. /// </summary> /// <param name="container">The current container.</param> public MEFDependencyResolver(CompositionContainer container) { Throw.IfArgumentNull(container, "container"); this.container = container; } #endregion #region Methods /// <summary> /// Gets an instance of the service of the specified type. /// </summary> /// <param name="type">The type.</param> /// <returns>An instance of the service of the specified type.</returns> public object GetService(Type type) { Throw.IfArgumentNull(type, "type"); string name = AttributedModelServices.GetContractName(type); try { return container.GetExportedValue<object>(name); } catch { return null; } } /// <summary> /// Gets all instances of the services of the specified type. /// </summary> /// <param name="type">The type.</param> /// <returns>An enumerable of all instances of the services of the specified type.</returns> public IEnumerable<object> GetServices(Type type) { Throw.IfArgumentNull(type, "type"); string name = AttributedModelServices.GetContractName(type); try { return container.GetExportedValues<object>(name); } catch { return null; } } #endregion
Code language: PHP (php)

}
The interface (contract) expects us to implement a GetService method, for resolving a single instance of a type, and a GetServices method, which resolves all instances of a type. We can easily use our CompositionContainer to resolve these instances, we just have to make a quick call to AttributedModelServices to get our contract name, and then get the container to do the rest.

Minimising the wire-up
With our IDependencyResolver ready to go, I wanted to look at how we can minimise the wire-up. In other systems that I’ve designed, I’ve relied on a bootstrapping process which performs a set of startup tasks required to get the application in a ready state. We can do the same thing with our web application. Firstly, lets have a look at a task:

/// /// Defines the required contract for implementing a bootstrapper task. ///
public interface IBootstrapperTask
{
#region Methods
/// /// Runs the task. ///
///
void Run(CompositionContainer container);
#endregion
}
It’s quite simple, but it becomes a powerful mechanism when its mixed with our customised export attribute:

/// /// Marks the target class as an exportable bootstrapper task. ///
[AttributeUsage(AttributeTargets.Class), MetadataAttribute]
public class ExportBootstrapperTaskAttribute : ExportAttribute, INamedDependencyMetadata
{
#region Constructor
/// /// Initialises a new instance of . ///
/// The name of the task.
/// Any named dependencies this task is explicitly dependent on.
public ExportBootstrapperTaskAttribute(string name, params string[] dependencies) : base(typeof(IBootstrapperTask))
{
Throw.IfArgumentNullOrEmpty(name, “name”);

Dependencies = dependencies; Name = name; } #endregion #region Properties /// <summary> /// Gets the dependencies. /// </summary> public string[] Dependencies { get; private set; } /// <summary> /// Gets the name of the task. /// </summary> public string Name { get; private set; } #endregion
Code language: PHP (php)

}
This custom export attribute implements a metadata contract, INamedDependencyMetadata which allows us to export additional metadata out with the task itself. The attribute supports a Name and Dependencies property, Name being quite self-explanatory, whereas the Dependencies property is an array of names, representing tasks that the target task is dependent on running. We resolve these dependencies with a special kind of list, the DependencyList.

The DependencyList is an implementation of a topological sort (based on Patrick Dewane’s article here). It allows us to sort an arbitrary set of elements into their dependent order, so in our case, we are sorting our tasks to ensure they are run in the correct order. The DependencyList really deserves a blog post to itself, but I’ll include the code in this project for your feedback.

Now we have a design for our tasks, we can look at our Bootstrapper, and how it manages our tasks.

/// /// Performs bootstrapping operations. ///
public static class Bootstrapper
{
#region Fields
private static bool initialised;
private static readonly Mutex mutex = new Mutex();
#endregion

#region Properties /// <summary> /// Gets the current container. /// </summary> public static CompositionContainer Container { get; private set; } #endregion #region Methods /// <summary> /// Runs the bootstrapper. /// </summary> public static void Run() { if (Container == null) return; // Set the IDependencyResolver MVC uses to resolve types. DependencyResolver.SetResolver(new MEFDependencyResolver(Container)); // Run any bootstrapper tasks. RunTasks(); } /// <summary> /// Runs any required bootstrapper tasks. /// </summary> private static void RunTasks() { var tasks = Container.GetExports<IBootstrapperTask, INamedDependencyMetadata>(); var list = new DependencyList<Lazy<IBootstrapperTask, INamedDependencyMetadata>, string>( l => l.Metadata.Name, l => l.Metadata.Dependencies); foreach (var task in tasks) list.Add(task); foreach (var task in list) task.Value.Run(Container); } /// <summary> /// Sets the factory used to create a container. /// </summary> /// <param name="factory">The container factory.</param> public static void SetContainerFactory(ICompositionContainerFactory factory) { Throw.IfArgumentNull(factory, "factory"); mutex.WaitOne(); if (initialised) return; try { var container = factory.CreateContainer(); Throw.If(container == null).As<InvalidOperationException>(() => CoreResources.FactoryDidntCreateContainer); Container = container; // Add the container to itself so it can be resolved. var batch = new CompositionBatch(); batch.AddExportedValue(container); container.Compose(batch); initialised = true; } finally { mutex.ReleaseMutex(); } } #endregion
Code language: PHP (php)

}
The bootstrapper is designed to take an instance of ICompositionContainerFactory, this allows us to create our CompositionContainer outside of the bootstrapper, and makes it much easier to test. The bootstrapper creates an instance of the container using this factory, and then starts running the tasks. Oh, the bootstrapper also takes care of wiring up our IDependencyResolver.

With our bootstrapper in place, out startup code is reduced significantly:

public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
Bootstrapper.SetContainerFactory(new DefaultCompositionContainerFactory());
Bootstrapper.Run();
}
}
Nice eh?

Example Tasks: Route Registration
Much like the previous project, I’ve taken the route registration out of the global class, but this time I’ve made it a bootstrapper task. What this means is that, we don’t have to worry about explicity calling any route registration, the bootstrapper task will fire automatically, and any instances of our IRouteRegistrar contract will be used to register the specific routes. This is quite key, as when we move onto modelling a modular system, we don’t have to specifically run anything, we just label up an IRouteRegistrar for export, and it is automatically handled for us.

The contract:

/// /// Defines the required contract for implementing a route registrar. ///
public interface IRouteRegistrar
{
#region Methods
/// /// Registers any required routes. ///
/// The route collection to register routes with.
void RegisterRoutes(RouteCollection route);
#endregion
}
Again, we keep out contracts nice and simple! Next, let’s add in our bootstrapper task:

/// /// Registers any required routes with the routing system. ///
[ExportBootstrapperTask(“RegisterRoutes”)]
public class RegisterRoutesBootstrapperTask : IBootstrapperTask
{
#region Methods
/// /// Runs the task. ///
///
public void Run(CompositionContainer container)
{
Throw.IfArgumentNull(container, “container”);

var registrars = container .GetExports<IRouteRegistrar, IOrderedMetadata>() .OrderBy(r => r.Metadata.Order) .Select(r => r.Value); var routes = RouteTable.Routes; foreach (var registrar in registrars) registrar.RegisterRoutes(routes); } #endregion
Code language: PHP (php)

}
With that all done, we are in a good position for a fuller MVC3+MEF architecture. Thanks to MEF, adding a custom filter, controller, etc. is as simple as [Export]ing. I’ll flesh this out in the future. The project is attached, let me know what you think.

Download VS2010 Project

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Post