Disclaimer

First things first, don’t consider this sample an official sample. It is just something I’ve created to illustrate some capabilities of MEF when integrated with ASP.NET MVC in the user-level (as opposed to framework level).

Also, if you would try to do this integration yourself, you’ll notice at some point that you’ll need to attach metadata to your controllers, so you can grab the right one on the controller factory. In this sample I provide a custom catalog that is aware of controllers, so it adds the metadata itself saving us the burden. Of course, this adds to complexity..

As MEF targets the extensibility story, this sample is an ASP.NET MVC app that can be “augmented” by new modules. The main app is a shell with almost no functionality. New modules add navigation, controllers, services and consume the services are present on the system.

The first extension module provide adds Wiki support to the main application. The second, a forum module.

Bear in mind that everything is fake. I’m showing how to leverage the extensibilities capabilities of MEF, and not how to design a CMS, Forum or Wiki :-)

In a real world app a similar extensibility mechanism in a web app would also have to deal with database/versioning issues, content resource (additional css, js, images), and so forth.

Download

MvcWithMEF.zip

Walkthrough

You may start by running the app. You should see the following on your browser:

scl1

The links above (forum, wiki) were added dynamically by modules loaded.

Where everything starts

The Global.asax.cs holds our bootstrap code:

   1:  protected void Application_Start()
   2:  {
   3:    HostingEnvironment.RegisterVirtualPathProvider(
   4:      new AssemblyResourceVirtualPathProvider());
   5:   
   6:    RegisterRoutes(RouteTable.Routes);
   7:   
   8:    var catalog = new CatalogBuilder().
   9:      ForAssembly(typeof(IModuleInstaller).Assembly).
  10:      ForMvcAssembly(Assembly.GetExecutingAssembly()).
  11:      ForMvcAssembliesInDirectory(HttpRuntime.BinDirectory, "*Extension.dll"). 
  12:      Build();
  13:   
  14:    _container = new CompositionContainer(catalog);
  15:   
  16:    var modules = _container.GetExportedObjects<IModuleInstaller>();
  17:    var navService = _container.GetExportedObject<NavigationService>();
  18:   
  19:    InitializeModules(modules, navService);
  20:   
  21:    ControllerBuilder.Current.SetControllerFactory(
  22:      new MefControllerFactory(_container));
  23:  }

The first line sets up a custom VirtualPathProvider that check assembly resources for content. Nothing new.

Lines 8-12 defines our catalog, which encapsulates the discovery mechanism on MEF. It also provides us opportunities to customize things. In this case, we want to discover things on the Core assembly (line 9), on the executing assembly/web project assembly (line 10), and anything on the web app’s bin folder that ends with Extension.dll (line 11).

A container is created with a reference to the aggregation of all these catalogs. Right after we retrieve all IModuleInstaller implementations available and start all modules, which is to say we give them a chance to change/add URL routes and add navigation information.

Finally, we create a custom IControllerFactory instance and set up ASP.NET MVC to use it (line 21).

The MEF Controller Factory
   1:  public class MefControllerFactory : IControllerFactory
   2:  {
   3:    private const string ControllerExportEntryName = "controllerExport";
   4:    private readonly CompositionContainer _container;
   5:   
   6:    public MefControllerFactory(CompositionContainer container)
   7:    {
   8:      _container = container;
   9:    }
  10:   
  11:    public IController CreateController(RequestContext requestContext, string controllerName)
  12:    {
  13:      var controllerExport = _container.GetExports<IController>().
  14:        Where(exp => 
  15:          exp.Metadata.ContainsKey(Constants.ControllerNameMetadataName) &&
  16:          exp.Metadata[Constants.ControllerNameMetadataName].
  17:            ToString().ToLowerInvariant().
  18:            Equals(controllerName.ToLowerInvariant())).
  19:          FirstOrDefault();
  20:   
  21:      if (controllerExport == null)
  22:      {
  23:        throw new HttpException(404, "Not found");
  24:      }
  25:   
  26:      requestContext.HttpContext.Items[ControllerExportEntryName] = controllerExport;
  27:   
  28:      return controllerExport.GetExportedObject();
  29:    }
  30:   
  31:    public void ReleaseController(IController controller)
  32:    {
  33:      var export = HttpContext.Current.Items[ControllerExportEntryName] as Export<IController>;
  34:   
  35:      if (export != null)
  36:      {
  37:        _container.ReleaseExport(export);
  38:      }
  39:    }
  40:  }

This implementation, albeit short, shows interests aspects. First, on the CreateController, we get all exports of IController – which is different from getting all _instances_ of IController. Then, we use the metadata associated with those Exports to find the controller we need to get an instance.

We hold this export instance for the duration of this request (HttpContext.Items), so we can use it on ReleaseController. This relies on the assumption that in a request only one controller is instantiated.

The ReleaseController get back that export instance and calls Container.ReleaseExport, which will go over all the object graph, and properly release and dispose NonShared / Disposable instances. If you miss this step you will be in a route to a memory bloat as every controller is non shared and disposable.

The MVC Catalog

This one is a bit more complex, but remember, it is an optional step. I just added it because I’m lazy and didn’t want to add metadata to every controller (it would be a violation of DRY too.)

public class MvcCatalog : ComposablePartCatalog, ICompositionElement
{
  private readonly Type[] _types;
  private readonly object _locker = new object();
  private IQueryable<ComposablePartDefinition> _parts;
 
  public MvcCatalog(params Type[] types)
  {
    _types = types;
  }
 
  // More constructors //
 
  public override IQueryable<ComposablePartDefinition> Parts
  {
    get { return InternalParts; }
  }
 
  internal IQueryable<ComposablePartDefinition> InternalParts
  {
    get
    {
      if (_parts == null)
      {
        lock(_locker)
        {
          if (_parts == null)
          {
            var partsCollection = 
              new List<ComposablePartDefinition>();
 
            foreach(var type in _types)
            {
              var typeCatalog = new TypeCatalog(type);
              var part = typeCatalog.Parts.FirstOrDefault();
 
              if (part == null) continue;
 
              if (typeof(IController).IsAssignableFrom(type))
              {
                part = new 
                  ControllerPartDefinitionDecorator(
                    part, 
                    type, 
                    type.Name, 
                    type.Namespace);
              }
 
              partsCollection.Add(part);
            }
 
            Thread.MemoryBarrier();
 
            _parts = partsCollection.AsQueryable();
          }
        }
      }
 
      return _parts;
    }
  }

What we’re doing here is: for each type discovered – is a MEF part – we also check if it happens to be a controller. If so, we decorate that part to add an additional export. This export has all the extra metadata that we need to query for that controller later on.

Wiring

As we’re using MEF for extensibility we might just as well use it for typical composition – as long as our requirements are low compared to the use of a full fledge IoC Container.

[Export, PartCreationPolicy(CreationPolicy.NonShared)]
public class ForumController : BasePublicController
{
  private readonly ForumService _forumService;
 
  [ImportingConstructor]
  public ForumController(ForumService forumService)
  {
    _forumService = forumService;
  }
 
  public ActionResult Index()
  {
    ViewData["forums"] = _forumService.GetEnabledForumsRecentActivity();
 
    return View(ViewRoot + "Index.aspx");
  }
}

In this controller we import (depend) ForumService, which itself depends on IForumRepository. If you don’t mind the extra noise of attributes, MEF can also cover this aspect of your app – but remember, it wasn’t built to be an IoC Container as the ones out there – more on that in another day.

Conclusion

In the user-level integration MEF plays very well with ASP.NET MVC to enable extensibility on web apps. Of course, this is not the whole story. Like I mentioned, database schema updates/version, making additional content available (js, css, images) and UI Composition are challenging things themselves, but solvable – been there, done that.

With MEF you have one less thing to worry about in this challenging problem space.

Update: Kamil spot the fact that my decorator is leaving out one member, and that essentially makes it ignore the creation policy (which is attached to the part definition, not exports). Sample updated.