Introducing MEF Lightweight Composition and an Updated Composition Provider for ASP.NET MVC [Nick]

With the second major release of MEF the team has significantly streamlined support for web applications. In November we previewed the new Composition Provider for ASP.NET MVC, and we’ve had great feedback supporting this direction. In this post we share some exciting new progress. — The BCL Team.

We’ve had many positive comments from people who have used the MEF Composition Provider for ASP.NET MVC, so we spent additional time looking at how we can improve this component and apply the same model to a wider range of applications.

There are two opportunities we identified in this process. We are setting out to address them today with a new preview, MEF 2 Lightweight Composition, which is available via NuGet and CodePlex (package details at the end of this post):

  • By optimizing the scenarios appropriate for web applications, a much higher throughput can be achieved (composed graphs/second)
  • Some tweaks to the lifetime model used in the Composition Provider make it easier to share parts from a web application with alternative hosts (back-end processes, worker roles, etc.)

Let’s take a look at these in detail.

High-Throughput/Concurrent Composition

The previous release of the Composition Provider used nested CompositionContainers to drive the creation of parts. Those familiar with MEF on the desktop will immediately recognize CompositionContainer, which is the core of the MEF hosting API.

Our V1 goals called for CompositionContainer to be optimized heavily for startup rather than throughput, given the requirements of large, extensible desktop applications like Visual Studio. CompositionContainer also supports a wide range of advanced composition scenarios that are not necessary for web applications. These include recomposition, stable composition AKA rejection, and other features that assume a low throughput, low contention execution environment. The architecture that supports these features incurs an overhead, particularly in the way concurrency is handled.

This caused us to stop and think, because it is important that we set a good foundation for MEF on the server. Web applications are highly concurrent and will continue to become more so as servers handle ever-greater volumes of requests. While composition throughput is irrelevant in many real-world applications, we would like to keep it off the performance radar entirely, so that MEF can be utilized broadly in frameworks targeting the web and cloud.

MEF 2 Lightweight Composition includes a stripped-down ‘lightweight’ version of CompositionContainer built specifically for high-throughput, concurrent composition. The Composition Provider for ASP.NET MVC uses this container to provide the same user experience as in previous versions, but with many times the throughput of the full CompositionContainer and nearly linear scaling with the addition of CPUs.

Broader Lifetime Model

The lifetime model used in the previous Composition Provider relied on a per-HTTP-request default lifetime for parts, and the [ApplicationShared] attribute to mark ‘singletons’. These together make an excellent environment for rapid web development, but they make deep assumptions that the parts being written are for web applications only.

Very few large projects produce only web entry points. From message handlers, job and batch processes to administrative console tools, large applications (the kind targeted by MEF) are frequently supported by a suite of different hosts. We want the lifetime defaults used in the Composition Provider to enable reliable sharing of parts between all of these environments (when using Lightweight Composition).

So, the updated Composition Provider employs a new, simplified lifetime model that is optimized for transactional environments. How to best take advantage of this is the topic of upcoming blog posts, but the basics are very simple to explain.

  • By default, parts created by the Composition Provider are non-shared
  • Adding a [Shared] attribute will make the part shared at the application-level, i.e. with singleton semantics
  • The [Shared] attribute accepts a parameter describing the ‘boundary’ within which the part will be shared, so [Shared(Boundaries.HttpRequest)] will cause an instance of the part to be shared/released along with the lifecycle of processing a web request

To see the new lifetime model in action, you can view the demo and samples in the MEF CodePlex repository. These additions apply to the lightweight model only, we are still providing CompositionScopeDefinition with the full CompositionContainer.

How Lightweight Composition Works

Apart from initialization/bootstrapping, there are very few visible differences between applications using Lightweight Composition and those using the full CompositionContainer.

For example, the following part works with Lightweight Composition:

 [Export(typeof(IMessageHandler))]
public class MessageHandler : IMessageHandler
{
    [ImportingConstructor]
    public MessageHandler(IDatabaseConnection connection)
    {
    }
}

We see the familiar [Export] and [ImportingConstructor] attributes with their usual meanings. Lightweight Composition covers:

  • The [Export] , [Import] , [ImportingConstructor] , [ImportMany] , [PartMetadata] and [MetadataAttribute] attributes, including custom attributes
  • Class, interface and property exports
  • Constructor and property imports
  • IPartImportsSatisfiedNotification for OnImportsSatisfied()
  • IDisposable

To make a highly-optimized implementation possible, however, some features provided by the full CompositionContainer are not supported:

  • Imports or exports on private fields, properties or constructors – these limit our ability to generate MSIL for composition
  • Inherited exports, field exports, static exports or method exports – these slow down the part discovery/startup process
  • Importing into custom collections – this is complicated to implement efficiently but can be revisited in the future
  • Imports with RequiredCreationPolicy or ImportSource – these are incompatible with the simplified lifetime model
  • ICompositionService – this is inefficient in server scenarios; IExportProvider is used as a substitute with similar functionality

Perhaps not by coincidence, we have found ourselves recommending against using some of these features, even in environments where they are supported. Private imports and exports, for example, cause issues for testability, and field imports/exports prevent proper encapsulation.

In this preview, there may be further subtle differences, but for the most part these will be addressed with fixes and updates. Notably, the current preview may have issues running in partial trust.

On the hosting side, Lightweight Composition is a lot more minimalistic than the full Composition Container. Without rejection or recomposition, very few of the catalogs or CompositionContainer APIs are useful. Rather than modify these classes, we have opted instead to provide a simple configuration class, ContainerConfiguration. The configuration API is type/assembly-based, leaving discovery (e.g. directory scanning) to the developer for the time being.

Constructing a simple container looks like this:

 var configuration = new ContainerConfiguration()
    .WithAssembly(typeof(Program).Assembly);

using (var container = configuration.CreateContainer())
{
    var greeter = container.Value.GetExport<Greeter>();
    greeter.Greet();
}

ContainerConfiguration provides the methods WithPart() and WithAssembly() as well as a few more simple helper methods that cover the majority of scenarios. The return type of CreateContainer() is ExportLifetimeContext<IExportProvider> , a clue that the new hosting code relies very strongly on ExportFactory<T> for controlling lifetime.

If you’re using ASP.NET MVC, all of this is hidden within the Composition Provider and doesn’t need to be set up manually.

Over the coming months we’ll post more detail on how to configure and use the Lightweight Composition feature; at the moment, the source code provided on CodePlex is the best place to find examples: the /oob/demos folder has code showing the new lifetime model and extension points.

Getting Started

The Lightweight Composition feature is being released via NuGet in conjunction with the updated Composition Provider for ASP.NET MVC. These two additional components will work on top of the MEF 2 assemblies that are shipping with the .NET Framework 4.5 Beta.

The two packages we're releasing are:

Important: These packages are currently marked as 'alpha' releases. The Library Package Manager dialog does not currently show prerelease packages, so to install them you need to use the Package Manager Console:

     PM> Install-Package -Pre Microsoft.Mef.MvcCompositionProvider

Installing the Microsoft.Mef.MvcCompositionProvider package into your ASP.NET MVC application will automatically pull in the other package as a dependency.

Note, we currently sign the assemblies in these packages with our public CodePlex key, and not the private framework key.

You can find instructions for getting started on MSDN. There are some minor changes between the article and the current version of the NuGet packages – we'll reconcile these as soon as possible.

The Future

The release being made today is a technology preview and will change as we deepen our understanding of the approach and how best to utilize it in web and cloud apps.

By shipping out-of-band with NuGet, we are enabling a much more responsive lifecycle for these features, with follow-up releases possible on a timeline of weeks and months, rather than years. We hope this demonstrates our commitment to making MEF the best it can be in the cloud and on the web.

If you’re using MEF in one of the XAML-based environments, don’t worry – we haven’t forgotten you! Releasing Lightweight Composition in conjunction with the Composition Provider for ASP.NET MVC is just a first step in making MEF a more responsive, community-driven project and we will ensure that these benefits span all of the application models in which MEF is supported.

We hope you find some interest and use in this functionality. We’d love to hear about your experiences, good and bad, on this blog or over at the MEF community site on CodePlex.