Out of the box, MEF provides you with limited options for defining the lifetime of exported parts. At its most basic you can define either transient or singleton lifetimes (CreationPolicy.NonShared and CreationPolicy.Shared respectively). We have to remember that although MEF can be used as an IoC container, it has been primarily designed to focus on managing composition rather than compile time dependencies. This goes part way to explaining why it doesn’t have broader lifetime management features baked in like other IoC container implementations. In short, out of the box, MEF has a much coarser level of granularity when it comes to to the management of dependencies.

When used in web-projects (either web-forms or MVC based), we will sometimes have the requirement to scope exported parts based on either the lifetime of a HTTP request, or an associated session. For example, we would like to be able to write code along the lines of this:


[Export(typeof(IFoo))]
[WebPartCreationPolicy(WebCreationPolicy.Session)]
public class SessionFoo : IFoo
{ }

Obviously the [WebPartCreationPolicy] attribute is something new, and in this example we’re aiming for one SessionFoo object to exist for the lifetime of the HTTP session. Likewise, we could use the value WebCreationPolicy.Request to indicate that an export exists for the lifetime of the current HTTP request. The good news is that MEF is highly extensible, and our web-scoped lifetime requirements can be easily implemented as a new catalog which simply transforms the part definitions found in another catalog through composition.

The code for this post can be found on my private fork of the MefContrib project (found at GitHub). In this fork I’ve introduced a new project called MefContrib.Web to support these (and other) web-specifc features.

Implementing a new WebScopedCatalog

Once the exports have been marked up with a [WebPartCreationPolicy] attribute, we need to do three things:

  1. Use an existing catalog to include the exported parts in the eventual CompositionContainer. For example, we could use a DirectoryCatalog or an AssemblyCatalog
  2. Pass the catalog into a newly created WebScopedCatalog
  3. Pass the WebScopedCatalog into our CompositionContainer

In web projects, we typically setup our CompositionContainer in the Global.asax. In the following example, we’ll use MVC:


public class MvcApplication : HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();

RegisterRoutes(RouteTable.Routes);

var catalog = new WebScopedCatalog(new DirectoryCatalog(Server.MapPath("~\\bin")));
var container = new CompositionContainer(catalog);

ControllerBuilder.Current.SetControllerFactory(new CompositionControllerFactory(container));
}
}

It’s important to note that you can also pass an AggregateCatalog into a new WebScopedCatalog allowing exports from a collection of underlying catalogs to be adorned with web-scoped lifetime semantics. In the above example we then give the CompositionContainer object to an MVC controller factory that knows how to create composed controllers. The WebScopedCatalog queries the parts of the underlying catalog and for any part definition which has a WebCreationPolicy metadata value, creates a new WebScopedComposablePartDefinition object in its place. It then stores a list of these parts unioned with those that didn’t so that it can return them when queried by MEF. Basically it transforms the underlying list of composable part definitions so that where one has a web-creation policy, the ComposablePartDefinition is replaced with the new WebScopedComposablePartDefinition object instead:


public class WebScopedCatalog : ComposablePartCatalog
{
private readonly IQueryable<ComposablePartDefinition> _parts;

public WebScopedCatalog(ComposablePartCatalog partCatalog)
{
if (partCatalog == null)
throw new ArgumentNullException("partCatalog");

var webScopedParts
= from part in partCatalog.Parts
let exportDef = part.ExportDefinitions.First()
where exportDef.Metadata.ContainsKey("WebCreationPolicy")
select part;

var nonScopedParts = partCatalog.Parts.Except(webScopedParts);

_parts
= webScopedParts.Select(p => new WebScopedComposablePartDefinition(p))
.Union(nonScopedParts)
.AsQueryable();
}

public override IQueryable<ComposablePartDefinition> Parts
{
get { return _parts; }
}
}

When MEF requires access to the part, the WebScopedComposablePartDefinition object simply inspects the value of the WebCreationPolicy metadata value, and then returns either a WebRequestScopedComposablePart or a WebSessionScopedComposablePart object based on that value:


public class WebScopedComposablePartDefinition : ComposablePartDefinition
{
private readonly ComposablePartDefinition _partDefinition;

internal WebScopedComposablePartDefinition(ComposablePartDefinition partDefinition)
{
_partDefinition = partDefinition;
}

public override ComposablePart CreatePart()
{
var policy = (WebCreationPolicy)_partDefinition.ExportDefinitions.First().Metadata["WebCreationPolicy"];

if (policy == WebCreationPolicy.Request)
return new WebRequestScopedComposablePart(_partDefinition.CreatePart());
else
return new WebSessionScopedComposablePart(_partDefinition.CreatePart());
}

...
}

The implementation of these web-scoped aware composable parts look in either the HTTPContext object’s session or current items dictionaries for a created object and returns the object if one is found. If one isn’t found, it delegates to the underlying ComposablePart to create the object before storing it in the associated dictionary thereby caching the exported object at either the request or session boundary. For example, the WebSessionScopedComposablePart is defined as follows:


public class WebSessionScopedComposablePart : WebScopedComposablePart
{
internal WebSessionScopedComposablePart(ComposablePart composablePart)
: base(composablePart)
{ }

public override object GetExportedValue(ExportDefinition definition)
{
string sessionKey = string.Format("__Session_{0}", Key);

var obj = CurrentHttpContext.Session[sessionKey];

if (obj != null)
return obj;

obj = CreatePart();

CurrentHttpContext.Session[sessionKey] = obj;

return obj;
}
}

Tagged with: