ExportFactory in MEF 2 [Alok]

This post discusses features in the preview version of MEF, and some details may change between now and the full release.

In the first version of MEF there are only two notions of lifetime: A shared global lifetime or a per instance lifetime. In MEF 2 Preview 4 new support has been added to enable a finer grained control over the lifetime and sharing of parts. Before we can cover those changes in detail, we need to introduce ExportFactory<T> .

Let us consider a simple application scenario of a RequestListener, which spawns a RequestHandler, which uses a DatabaseConnection to connect to a data source (Fig 1):

Let us also consider the following parts that implement the RequestHandler and DatabaseConnection.

     [Export]
    public class RequestHandler
    {
        [ImportingConstructor]
        public RequestHandler(DatabaseConnection connection)
        {
        }
    }

    [Export]
    public class DatabaseConnection
    {
        public DatabaseConnection()
        {
        }
    }

In MEF version 1...

To better illustrate the new capabilities we’ve added in MEF 2, some of the scenarios that could be achieved in MEF version 1 are discussed below.

Scenario 1: RequestHandler and DatabaseConnection instances are shared

When we first start building our app, we are being very conservative about resources and not knowing our load we spin up a single instance of RequestHandler and DatabaseConnection

We could instantiate an instance of the RequestHandler using the following code.

 TypeCatalog global = new TypeCatalog(
    typeof(RequestHandler), typeof(DatabaseConnection));
var container = new CompositionContainer(global);
var requestHandler = container.GetExportedValue<RequestHandler>();

Regardless of the number of times GetExportedValue() is called, the same instance of the RequestHandler is always returned (Fig 2).

Scenario 2: Separate instances of RequestHandler and DatabaseConnection

Now we realize that the throughput for our system is really poor, since we can only process one request at a time. So in order to increase our throughput, we spin up new instance of RequestHandler and DatabaseConnection for every request that comes in. In order to achieve this with MEF we can put a PartCreationPolicyAttribute on RequestHandler and DatabaseConnection:

     [PartCreationPolicy(CreationPolicy.NonShared)]
    public class RequestHandler { … }

    [PartCreationPolicy(CreationPolicy.NonShared)]
    public class DatabaseConnection { … }

Now, with the same invoking code to GetExportedValue() , we now get new instances for RequestHandler and DatabaseConnection every time.

Scenario 3: RequestHandler instances created and DatabaseConnection instance is shared

Our throughput is now up, but creating a new database connection per request is really taking a toll on our database server which can handle only a few open connections at a time. To ease the load on our database server we hence decide, that we will share a single instance of a DatabaseConnection, among multiple RequestHandlers. This will give us our throughput improvements without overloading our database server. We can achieve the following by getting rid of the PartCreationPolicy we have on the DatabaseConnection class, so it would now look like the following:

     public class DatabaseConnection { … }

With the removal of this attribute, we now get only a new instance of the RequestHandler every time GetExportedValue() is called, which all share a single instance of the DatabaseConnection.

In MEF 2... introducing ExportFactory<T>

The above scenarios that we have seen are the sharing scenarios that could be achieved using MEF in the past releases. However there is one glaring omission in the matrix that we presented above. What if I wanted a multiple instance of a dependency from a single instance of what it was depending on? To put it in the context of our example, how does the RequestListener class which was the first block in our diagram create multiple instances of the RequestHandler.

This brings us to:

Scenario 4: Single instance of RequestListener and multiple (dynamic) instances of RequestHandler

In order to enable this scenario, in the latest version of MEF, we introduce a new class called the ExportFactory<T> . ExportFactory<T> may be familiar to Silverlight 4 developers, since it was shipped as a part of the Silverlight 4 SDK. The export factory allows us to create new instances of dependencies and control the lifetime of the created parts.

Let us go back to our initial block diagram and write out the code for the RequestListener class.

     [Export]
    public class RequestListener
    {
        ExportFactory<RequestHandler> _factory;

        [ImportingConstructor]
        public RequestListener(ExportFactory<RequestHandler> factory)
        {
            _factory = factory;
        }

        public void HandleRequest()
        {
            using (var instance = _factory.CreateExport())
            {
                instance.Value.Process();
            }
        }
    }

Now we see in the code that instead of importing a RequestHandler as a depedency, we import an ExportFactory<RequestHandler> . MEF treats ExportFactory<T> as a special class, and will automatically hook it up to the part providing T. We then added a HandleRequest() member, which does the work of instantiating the RequestHandler. The CreateExport() method creates an ExportLifetimeContext<T> , which implements IDisposable, which is used to control the lifetime of the objects created by the factory. Since the listener is creating the parts, we can get also get rid of the PartCreationPolicy attribute on the RequestHandler. Putting all the pieces together a call to:

 TypeCatalog global = new TypeCatalog(typeof(RequestHandler), typeof(DatabaseConnection) , typeof(RequestListener));
var container = new CompositionContainer(global);
var requestListener = container.GetExportedValue<RequestListener>();
requestListener.HandleRequest();

will result in the following composition graph:

Stay tuned for part two of this post where we talk about some more sharing scenarios and how we can use the new scoping enhancements in MEF in conjunction with ExportFactory<T> to accomplish these.