Sharing with CompositionScopeDefinition 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 previous post, we introduced ExportFactory<T> and demonstrated how it can be used to control the lifetimes of MEF parts. In this article we will talk about a new type MEF called CompositionScopeDefinition that can be used in conjunction with ExportFactory<T> to control sharing. Let’s take a scenario that we could not achieve using what we know currently and how CompositionScopeDefinition provides the needed control.

Important note: CompositionScopeDefinition is geared towards more advanced, desktop application scenarios. If you're using MEF in an ASP.NET MVC web application and want to control sharing and lifetime on a per-HTTP-request level, you should use the pre-defined ASP.NET MVC integration.

Lets go back to our application block diagram that we built in Part 1 of this article.

Now let’s add a few details to this architecture. As our application has grown more complex, we have decided to factor out the data access part into a separate component called DataAccessLayer, which handles manages the connection to the database and reading and writing to it. In addition to this we also want to add a logger, which also logs some tracking information to the same database. So our slightly expanded functional diagram looks like the following.

In the context of our scenario, all data access within a single request should be made through the same database connection. In the default configuration, MEF will share a single DatabaseConnection across the application. How do we instruct MEF to share the database connection per request? Lets start by laying out the class definitions for the different parts.

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

        public RequestListener(ExportFactory<RequestHandler> handlerFactory)
        {
            _handlerFactory = handlerFactory;
        }

        public void HandleRequest()
        {
            using (var request = _handlerFactory.CreateExport())
            {
                request.Value.Process();
            }
        }
    }

    [Export]
    public class RequestHandler
    {
        [ImportingConstructor]
        public RequestHandler(DataAccessLayer dataAccess, Logger logger)
        {
        }

        public void Process()
        {
            //Do stuff using DataAccess and Logger
        }
    }

    [Export]
    public class DataAccessLayer
    {
        [ImportingConstructor]
        public DataAccessLayer(DatabaseConnection conn)
        {
        }
    }

    [Export]
    public class Logger
    {
        [ImportingConstructor]
        public Logger(DatabaseConnection conn)
        {
        }
    }

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

Introducing CompositionScopeDefinition

Scenario 5: Sharing within the scope of an ExportFactory<T> instantiation

As we can observe as we had in the previous article the RequestListener class has an ExportFactory to create instances of the RequestHandler class. The RequestHandler now depends on two classes the DataAccess and the Logger class, both of which depend on the DatabaseConnection class. What we want to achieve looks like the following figure.

If we look from the DatabaseConnection down to the root of the composition at RequestListener, we can see that from a composition beginning at the RequestHandler (the highlighted portion), we can achieve the sharing using standard MEF we have discussed in part 1 of this article. This is one way to figure out how to divide your catalog into scopes, when you hit a sharing pattern that cannot be achieved by using the simple MEF options. We would then need to write the following code to obtain the desired sharing.

 var listenerLevel = new TypeCatalog(typeof(RequestListener));
var requestLevel = new TypeCatalog(
    typeof(RequestHandler), 
    typeof(Logger), 
    typeof(DataAccessLayer), 
    typeof(DatabaseConnection));

var container = new CompositionContainer(
     new CompositionScopeDefinition(listenerLevel, 
         new [] { new CompositionScopeDefinition(requestLevel, null) }));

var requestListener = container.GetExportedValue<RequestListener>();
requestListener.HandleRequest();
requestListener.HandleRequest();  

The types making up the scenario are distributed between two catalogs — those that should be created and shared at the request listener level, and those that need to be created and shared at the per-request level. When an ExportFactory<RequestHandler> is imported into the request listener, the container sees that the RequestHandler type is within a child scope, so the child scope definition is used to determine the components (including the DatabaseConnection) that should be shared in the child scope.

In order for a parent scope to be able to discover a part in a child scope, it has to be imported as an ExportFactory<T> . If the RequestListener imported the RequestHandler type and not an ExportFactory<RequestHandler> , a CompositionException would be thrown.

How have you used CompositionScopeDefintion? Did you find it easy to use? Are there still sharing scenarios that you cannot achieve? Leave us a message in the comments or in the dicussion forum on the MEF CodePlex site.