C H A P T E R  4

Image

MEF 2 in 4.5

The first version of the Managed Extensibility Framework (MEF) was released as part of .NET 4 and with .NET 4.5 we see the release of MEF 2.0. The MEF team has added a number of new features that will be the focus of this chapter. These features include, but are not limited to, improved lifetime management, convention-based parts registration and, via NuGet, a new lightweight composition engine for web and Windows 8 apps.

In this chapter, we are going focus on providing an overview of each of the key new features in MEF with some simple examples of how they can be implemented. Before we discuss the new features, we will provide a quick recap of what MEF is.

What Is MEF?

The concept of extensibility is not new. At some point, you have probably rolled your own, but a custom solution becomes limited especially if you want to open your application to third parties to provide extensions. What MEF offers is a standardized way to provide application extensibility that is readily available by being part of the .NET Framework.

With MEF 1, the focus was solely on extensibility and there was clear delineation between it and IOC containers. On the other hand MEF 2, with the introduction of the RegistrationBuilder, support for open generics, and more granular lifetime management, the distinction has become less clear. The focus is still on extensibility, but in version 2 basic IOC is now also possible.

To provide this extensibility, MEF uses attributes to declare what a class consumes and what it offers. For example, in the following code snippet, the class ProjectView is decorated with the Export attribute that identifies it as a dependency. The SomeViews class imports ProjectView, and this is done simply by decorating the appropriate property with the Import attribute:

[Export]
public class ProjectView : UserControl
{
}

[Export]
public class SomeViews
{
    [Import]
    public ProjectView AProjectView{get; set;}
}

To pull all of this together, we write some bootstrapping code for MEF, commonly in the application’s entry point, to kick off the composition:

public partial class App : Application
{
   [Import]
   public SomeViews ViewsWeNeed {get; set;}
   public App()
   {
        this.Startup += new StartupEventHandler(App_Startup);
   }
  
  void App_Startup(object sender, StartupEventArgs e)
  {
    var catalog = new DirectoryCatalog(@".");
    var container = new CompositionContainer(catalog);
    container.ComposeParts(this);
  }
}

Though a simple example, what we have done with MEF is create an object graph based on declarative information rather than using imperative code to do the assembly.

SOME KEY TERMINOLOGY

What Is New in .NET 4.5

A number of new features have been added to MEF in .NET 4.5 that include the following:

  • Support for open generic parts
  • Convention-based part registration
  • Composition scoping enhancements
  • Diagnostic improvements

Many of these changes have been in response to user feedback and have been aimed at increasing the flexibility of MEF and addressing use cases that weren’t covered by the first version.

From here on in, we are going to look at these features. To keep things simple, we have chosen to use a console project for the sample code to avoid distraction by any unnecessary coding. For all of the examples that follow you will need, at a minimum, to add a reference to System.ComponentModel.Composition.

In addition, please remember any code here is sample code and more than likely will not adhere to best practice.

Support for Open Generic Parts

With the first version of MEF, you could only export closed generic types. For example, take the following interface:

public interface IRepository<T> where T : class
{
   T FindById( int id);
   void Save (T item);
}

And then add the following classes:

[Export]
public class ProjectsWindow :Window
{
    [ImportingConstructor]
    Public ProjectsWindow (IRepository<Project> projects)
    {
        // use projects;
    }
}

[Export]
public class ClientsWindow :Window
{
    [ImportingConstructor]
    public ProjectsWindow (IRepository<Client> clients)
    {
        // use clients;
    }
}

With this interface, you would need to create a specific implementation for the project and client repositories as well as any others that were needed.

With MEF in .NET 4.5, you can negate the need for individual concrete implementations by defining an open generic class type that implements the IRepository interface:

[Export(typeof(IRepository<>))]
public class EntityFrameworkRepository<T> : IRepository<T> where T :class
{
   // implement generic repository
}

What this means is that for any property or constructer where you have defined an Import that uses IRepository, MEF will instantiate an instance of a closed generic type of EntityFrameworkRepository for the specified class.

Using Open Generic Types

Let’s look at an example of a repository factory that exposes two repositories whose actual concrete implementations are resolved at runtime using MEF:

  1. Create a new Console project.
  2. Add references for the required MEF assemblies.
  3. Add a new interface to the project and name it IData. Then add the following code:
    public interface IData<T>
    {
    int Id { get; set; }
    void Update(T source);
    }
  4. Add another interface name IRepository and update it as follows:
    public interface IRepository<T>
    {
    T FindById(int id);
    void Save(T item);
    }
  5. Now add two classes, one called Client and the other Project. Both of these implement IData<T> and should look something like this:
    public class Client : IData<Client>
    {
       public int Id { get; set; }
       public string CompanyName { get; set; }
       public string ContactName { get; set; }


       public void Update(Client source)
       {
         CompanyName = source.CompanyName;
         ContactName = source.ContactName;
       }
    }

    public class Project : IData<Project>
    {
       public int Id { get; set; }
       public string ProjectName { get; set; }


       public void Update(Project source)
       {
         ProjectName = source.ProjectName;
       }
    }
  6. Next, add a class called FakeRepository. This will be an open generic type class that implements the interface IRepository. For this class, you will need to add a using statement referencing System.ComponentModel.Composition. Notice that this class is decorated with Export attribute that specifies IRepository as its contract:
    [Export(typeof(IRepository<>))]
    public class FakeRepository<T> : IRepository<T> where T : class, IData<T>
    {
      IList<T> items;
      public int Count { get { return items.Count; } }
      public T FindById(int id)
      {
         return items.FirstOrDefault(i => i.Id == id);
      }

    public void Save(T item)
    {
      var existingItem = FindById(item.Id);
      if (existingItem == null)
      {
         items.Add(item);
      }
      else
      {
         existingItem.Update(item);
      }

    }

    public FakeRepository()
    {
         items = new List<T>();
      }
    }
  7. Add one more class called RepositoriesFactory. This class will expose your Client and Project repositories and in this instance handle the bootstrapping of MEF:
    public class RepositoriesFactory
    {
       [Import(typeof(IRepository<>))]
       public IRepository<Client> Clients { get; set; }
       
       [Import(typeof(IRepository<>))]
       public IRepository<Project> Projects { get; set; }
       
       public RepositoriesFactory()
       {
         var assemblyCatalog = new      AssemblyCatalog(Assembly.GetExecutingAssembly());
         var container = new CompositionContainer(assemblyCatalog);
         container.ComposeParts(this);
       }
    }

    In the RepositoriesFactory class, we have decorated the Clients and Projects properties with the Import attribute specifying the contract we expect for each. Then in the class’s constructor, the code to get MEF to resolve the imports has been added.

    Image Note This would normally be done at a higher level in the application.

  8. Finally, add some code to see this working. Open the Program.cs file and add the following code:
    static void Main(string[] args)
    {
       var repositories = new RepositoriesFactory();
       Console.WriteLine(GetTypeName(repositories.Clients.GetType()));
       Console.WriteLine(GetTypeName(repositories.Projects.GetType()));
       Console.ReadKey();
    }

    private static string GetTypeName(Type type)
    {
       return string.Format("{0}<{1}>", type.Name.Replace("'1",""),
              type.GetGenericArguments()[0]);
    }
  9. Press F5 and you should see something similar to Figure 4-1.
    Image

    Figure 4 1. Closed generic types from open type definition

As you can see, even though you only defined IRepository<T> for the Clients and Projects properties, MEF has instantiated them as closed types of FakeRepository.

Let’s extend this example to actually return some data.

  1. Add a new class with the name DummyData, add a using statement for System.ComponentModel.Composition, and code it as follows:
    public class DummyData
    {
      [Export]
      public IList<Client> FakeClientData
      {
        get {return GetFakeClientData();}
      }

      [Export]
      public IList<Project> FakeProjectData
      {
         get { return GetFakeProjectData();}
      }

    private IList<Client> GetFakeClientData()
    {
       return new List<Client> {
         new Client { Id = 1,
           CompanyName = "The Factory",
           ContactName = "Andy Warhol"} };
    }

    private IList<Project> GetFakeProjectData()
    {
       return new List<Project>{
         new Project{Id = 1,
           ProjectName="Campbell Soup Cans"}};
    }
    }
  2. Next, modify the FakeRepository class to include a new constructor that will allow you to pass in a list of items. This constructor is decorated with the ImportingConstructor attribute, which declares to the container what the part needs when instantiating it:
    [ImportingConstructor]
    public FakeRepository(IList<T> preLoadedItems)
    {
      if (preLoadedItems != null)
      {
        items = preLoadedItems;
      }
      else
      {
        items = new List<T>();
      }
    }
  3. Reopen Program.cs and modify it with the following:
    static void Main(string[] args)
    {
       var repositories = new RepositoriesFactory();
       var client = repositories.Clients.FindById(1);
       Console.WriteLine("Company Name:{0}", client.CompanyName);
       var project = repositories.Projects.FindById(1);
       Console.WriteLine("Project Name: {0}", project.ProjectName);
       Console.ReadKey();
    }
  4. Press F5 to see the output, which should be similar to Figure 4-2.
    Image

    Figure 4-2. Example with data

Convention-based Part Registration

In MEF 1.0, the only way to identify an export part was for it to be decorated with the Export attribute. With MEF 2.0, an alternative way of identifying export parts has been provided with the addition of the RegistrationBuilder. What the RegistrationBuilder provides is the means to define a set of rules for Identifying export parts without them having to be explicitly defined. Where this is particularly useful is in a scenario where MEF is used to compose the internal structure of an application rather than manage external dependencies. In this case, it is easier to define a convention for how parts should be written and to communicate that to the team rather than having to specify every detail of every part.

Image Note To use the RegistrationBuilder in your project, you will need to also include references to System.ComponentModel.Composition.Registration and System.Reflection.Context.

Creating Rules

To create a rule, you first need to define how to identify a part by using one of the three For*() methods. These three methods return a PartBuilder and are as follows:

  • ForType<T>(): This selects a single type of T.
  • ForTypesDerivedFrom<T>(): This will select any type derived from a contract type T, which may be a base class or interface.
  • ForTypesMatching(Predicate<Type> predicate): This method gives you the ability to define a Boolean selector to select types. For example, this can be used to select types based on a naming convention.

So, for example, within your company all view models are inherited from the base class ViewModelBase. Rather than having every view model decorated with the Export attribute, you can do the following in the MEF bootstrap code:

var registrationBuilder = new RegistrationBuilder();
registrationBuilder.ForTypesDerivedFrom<ViewModelBase>().Export<ViewModelBase>();
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly(),registrationBuilder);

Or as an alternative, if within your company the convention is that all view model class names must end with ViewModel, you could do the following:

registrationBuilder.ForTypesMatching(x => x.Name.EndsWith("ViewModel") && x.IsPublic)
.Export<ViewModelBase>();

And, finally, if it is a specific type and you just want to make sure that it is exported:

registrationBuilder.ForType<Logger>().Export();

Once you have defined a part or parts, you can then configure all aspects of a MEF part such as export interface, importing constructors, part creation policy, and part metadata. Say, for example, you wanted to set the creation policy for the view models. You could do the following:

registrationBuilder.ForTypesDerivedFrom<ViewModel>()
                             .SetCreationPolicy(CreationPolicy.NotShared)
                             .Export<ViewModelBase>();

With imports being defined at either the constructor or property level, the RegistrationBuilder provides the means for managing a selection of the appropriate constructor and properties for a given class. Let’s look at four examples.

  • By default, RegistrationBuilder will select the public constructor with the largest number of parameters. In this example, we are instead specifying that for any type of IRepository, RegistrationBuilder should use the constructor with the least number of parameters:
    registrationBuilder.ForTypesDerivedFrom(typeof(IRepository))
      .SelectConstructor(ctrs =>  
        {
          var minParameters = ctrs.Min(ctr => ctr.GetParameters().Length);
          return ctrs.First(ctr => ctr.GetParameters().Length == minParameters);
        });
  • Given a case where we know what the concrete type is, the constructor can be selected using a strongly typed syntax:
    registrationBuilder.ForType<Logger>()
        .SelectConstructor(pb => new Logger(pb.Import<IConnection>()));
  • In this example, again we know the explicit type we are using so we use ImportProperty() to explicitly identify the property we want to apply the import to:
    registrationBuilder.ForType<FakeFactory>()
        .ImportProperty(ff => ff.Tiles);
  • Finally, in this example, we use ImportProperties() to declare that for all public properties on the class we want MEF to manage the import:
    registrationBuilder.ForType<FakeFactory>()
    .ImportProperties(pi => pi.PropertyType.IsPublic);
Convention + Attributes

Choosing to use a convention-based programming model with MEF doesn’t automatically rule out the use of attributes. Instead, they become the means by which you can override registration conventions. This really comes in handy when you want to handle exceptions to a rule. Rather than creating overly verbose rules to handle all exceptions to the rule or having to create numerous single component rules, attributes can be applied to specific parts, methods, or properties to modify how a rule is applied. In other words, as Nick Blumhardt says, any “MEF attribute applied to a part will override any rules that affect the same aspect of the same member.”

If you are interested in learning more about the interaction of rules with attributes in MEF, I recommend you read Nick Blumhardt’s post on the topic, which can be found at http://blogs.msdn.com/b/bclteam/archive/2011/11/03/overriding-part-registration-conventions-with-the-mef-attributes-nick.aspx.

The End View

Personally, I like the explicit nature of attributes and believe it should be your first choice if you are developing an application that is going to be extended by third parties. On the other hand, if MEF is being used to manage internal development or you have an aversion to attributes, the RegistrationBuilder is a very powerful addition to the MEF armory.

Composition Scoping Enhancements

In MEF 1.0, there were only two types of lifetime that could be applied to an object:

  • A shared global lifetime
  • A per instance lifetime

Taking the conservative path, MEF by default assumes all parts to be shared unless the creation policy on a part was set to NonShared. Though functional, the application of the creation policy was limited in the scenarios it could address in terms of managing the lifetime of objects. To give finer-grained control of object lifetimes, MEF 2.0 now includes the ExportFactory (originally part of the Silverlight 4 SDK) and the CompositionScopeDefinition.

ExportFactory<T>

Simply put, the export factory allows us to more finely control the lifetime of dependencies by creating an ExportLifetimeContext<T>, which implements IDisposable.

The best way to see how this works is to code it up. So, let’s take the canonical example of a request listener that has a dependency on a request handler.

Before Implementing the Export Factory
  1. First, create a new Console project, add a reference to System.ComponentModel.Composition, and then create the following classes:
    • FakeDatabaseConnection
    • HandlerNameCreator
    • RequestHandler
    • RequestListener
  2. Open FakeDatabaseConnection.cs and add the following code. This class represents a database connection that will be shared by all of the request handlers:
    [Export]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public class FakeDatabaseConnection
    {
      private readonly DateTime dateTimeCreated;
      public FakeDatabaseConnection()
      {
        dateTimeCreated = DateTime.Now;
      }

      public string DatabaseName
      {
        get
        {
          return string.Format("Database {0}",
            dateTimeCreated.Millisecond);
        }
      }
    }
  3. Next, open the HandlerNameCreator.cs file and add the following code. This class allows you to assign a unique name to each instance of a request handler:
    [Export]
    public class HandlerNameCreator
    {
      int counter = 0;
      string prefix = "Handler";

      public string GetAHandlerName()
      {
       counter += 1;
       return string.Format("{0} {1}", prefix, counter);
      }
    }
  4. Next, open RequestHandler.cs. This class is used by the request listener to process requests. It has a dependency on FakeDatabaseConnection and HandlerNameCreator:
    [Export]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    public class RequestHandler
    {
      FakeDatabaseConnection dbConnection;
      public string HandlerName { get; private set; }
      
     [ImportingConstructor]
     public RequestHandler(FakeDatabaseConnection dbConnection,
       HandlerNameCreator nameCreator)
     {
       HandlerName = nameCreator.GetAHandlerName();
       this.dbConnection = dbConnection;
     }

    public string GetDatabaseName()
     {
       return this.dbConnection.DatabaseName;
     }
    }
  5. Now open the file RequestListener.cs and add the following code. This class exposes a single method that is called to handle a request and for this exercise will write out the names of the handler and the database:
    [Export]
    [PartCreationPolicy(CreationPolicy.NonShared)]
     public class RequestListener
    {
       private readonly RequestHandler handler;
       [ImportingConstructor]
       public RequestListener(RequestHandler handler)
      {
         this.handler = handler;
      }

      public void HandleRequest()
      {
         Console.WriteLine("{0}: {1}", handler.HandlerName,
         handler.GetDatabaseName());
      }
    }
  6. Finally, to glue this all together, open Program.cs and add the following code:
    class Program
    {
       public CompositionContainer Container { get; set; }
       static void Main(string[] args)
       {
         var p = new Program();
         var listener = p.Container.GetExportedValue<RequestListener>();
         for (int i = 0; i < 3; i++)
         {
           listener.HandleRequest();
           Console.WriteLine();
           Thread.Sleep(1000);
         }

         Console.ReadKey();
       }

       public Program()
       {
         var global = new TypeCatalog(
         typeof(RequestHandler),
         typeof(FakeDatabaseConnection),
         typeof(HandlerNameCreator),
         typeof(RequestListener));

         Container = new CompositionContainer(global);
       }
    }

In this code, we use the Program constructor to create a new CompositionContainer using a TypeCatalog. In the Main() method, we use the container to get an instance of RequestListener and then call the method HandleRequest() three times.

By pressing F5, you should see something similar to Figure 4-3.

Image

Figure 4 3. Result pre-ExportFactory

The thing to note here is that even though we set the creation policy on the RequestHandler to NonShared, the RequestListener used only one instance of it. So, what if you wanted to use a single instance of the RequestListener but with multiple instances of the RequestHandler? This is where ExportFactory<T> comes into play.

Implementing Export Factory
  1. First, open RequestHandler.cs and remove the PartCreationPolicy attribute.
  2. Next, open RequestListener.cs and modify the code so that it looks like the following:
    [Export]
    public class RequestListener
    {
       private readonly ExportFactory<RequestHandler> factory;
       [ImportingConstructor]
       public RequestListener(ExportFactory<RequestHandler> factory)
       {
         this.factory = factory;
       }

       public void HandleRequest()
       {
         using (var instance = factory.CreateExport())
         {
           var handler = instance.Value;
           Console.WriteLine("{0}: {1}", handler.HandlerName,
      handler.GetDatabaseName());
         }
       }
    }

Now if you run the program, what you should see is that a unique instance of RequestHandler has been created for each call to HandleRequest(). (See Figure 4-4.)

Image

Figure 4 4. Unique handler instances

As you can see, it doesn’t take much to implement the export factory to extend part lifetime management.

Using CompositionScopeDefinition

Along with the ExportFactory class, with this version of MEF we also see the introduction of another class called CompositionScopeDefinition, which provides even more fine-grained control over the lifetime of parts by grouping them into parent–child relationships.

In the previous example, each request handler shared the same database connection object, but what if we had a slightly more complex scenario? Here’s the scenario breakdown:

  • The request handler now accesses the database connection via a data access layer.
  • The request handler now also includes a logger.
  • The logger has a direct dependency on the database connection object.
  • For each call to the request handler, you want a new database connection that will be shared by both the logger and the data access layer.

Essentially, what we want to do in this scenario is have the instantiation and lifetimes of the logger, data access layer, and database connection “scoped” within the lifetime of the request handler.

Before CompositionScopeDefinition

Let’s try this without CompositionScopeDefinition to demonstrate the problem we are trying to solve:

  1. First, open up the project from the previous exercise if it isn’t open already and add two new classes: DataAccessLayer and Logger.
  2. Open DataAccessLayer.cs and add the following code:
    [Export]
    public class DataAccessLayer
    {
      private string dabId;
      FakeDatabaseConnection dbConnection;

      [ImportingConstructor]
      public DataAccessLayer(FakeDatabaseConnection dbConnection)
      {
        this.dbConnection = dbConnection;
        dabId = string.Format("Data Access Layer {0}", DateTime.Now.Millisecond);
      }

      public string DataAccessLayerInfo(string handlerName)
      {
        return string.Format("{0} using {1}",
                                dabId,
                                dbConnection.DatabaseName);
      }
    }
  3. Next, open Logger.cs and add the following:
    [Export]
    public class Logger
    {
      FakeDatabaseConnection dbConnection;
      private string loggerId;
            
      [ImportingConstructor]
      public Logger(FakeDatabaseConnection dbConnection)
      {
        this.dbConnection = dbConnection;
        loggerId = string.Format("Logger {0}",DateTime.Now.Millisecond);
      }

      public string LoggerInfo(string handlerName)
      {
        return string.Format("{0} using {1}",
                                loggerId,
                                dbConnection.DatabaseName);
      }
    }
  4. Next, modify the RequestHandler by opening the file and making the following changes:
    [Export]
    public class RequestHandler
    {
      private DataAccessLayer dataAccessLayer;
      private Logger logger;
      public string HandlerName { get; private set; }

      [ImportingConstructor]
      public RequestHandler(DataAccessLayer dataAccessLayer,
         Logger logger, HandlerNameCreator nameCreator)
      {
        HandlerName = nameCreator.GetAHandlerName();
        this.dataAccessLayer = dataAccessLayer;
        this.logger = logger;
      }

      public string GetRequestHandlerInfo()
      {
        var sb = new StringBuilder();
        sb.AppendLine(HandlerName);
        sb.AppendLine(dataAccessLayer.DataAccessLayerInfo(HandlerName));
        sb.AppendLine(logger.LoggerInfo(HandlerName));
        return sb.ToString();
      }
    }
  5. Next, make a minor change to the RequestListener’s HandleRequest() method to output a new message:
    public void HandleRequest()
    {
      using(var instance = factory.CreateExport())
      {
        var handler = instance.Value;
        Console.WriteLine(handler.GetRequestHandlerInfo());
      }
    }
  6. Finally, modify the Program constructor to include the two new classes:
    public Program()
    {
      var global = new TypeCatalog(
      typeof(RequestHandler),
      typeof(FakeDatabaseConnection),
      typeof(RequestListener),
      typeof(HandlerNameCreator),
      typeof(Logger),
      typeof(DataAccessLayer));

      Container = new CompositionContainer(global);
    }
  7. Press F5 to run the application and you should see something similar to Figure 4-5.
    Image

    Figure 4-5. Before implementing CompositionScopeDefinition

This result isn’t quite what we were after. Each instance of request handler is using the one instance of the data access layer and logger and they, in turn, are using the same database connection. You could try modifying the part creation policy of any of the child parts, but you still wouldn’t achieve the original aim. The solution is to use CompositionScopeDefinition class.

In essence, what the CompositionScopeDefinition class allows you to do is define a parent–child relationship of scoped catalogs. What this means is that given an underlying catalog of composable parts, the lifetime of these parts will determine the lifetime of any child catalog/parts associated with it. To go back to our current example, what we want is that for each instance of the request listener, the system creates a single unique instance of the logger, data access layer, and database connection.

Implementing the ComponentScopeDefinition
  1. First, open the FakeDatabaseConnection.cs file and remove the PartCreationPolicy attribute. By default, MEF treats all parts as shared.
  2. Next, open Program.cs and update the constructor as follows:
    public Program()
    {
      var requestLevel = new TypeCatalog(
          typeof(RequestHandler),
          typeof(FakeDatabaseConnection),
          typeof(Logger),
          typeof(DataAccessLayer));

      var listenerLevel = new TypeCatalog(
          typeof(RequestListener),
          typeof(HandlerNameCreator));
            
      Container = new CompositionContainer(
          new CompositionScopeDefinition(listenerLevel,
              new[] {
                  new CompositionScopeDefinition(requestLevel, null)
          }));
    }

With the previous code, you have now created two catalogs to group the objects by scope. In the constructor for the CompositionContainer, we pass in a new instance of CompositionScopeDefinition, which in turn takes a “parent” catalog and an array of CompositionScopeDefinitions (only one in the case) that contain the “child” catalog(s).

Press F5 and check the result of these changes. You should see something similar to Figure 4-6.

Image

Figure 4 6. Result after using CompositionScopeDefinition

Composition Scoping in Summary

With the addition of both the ExportFactory and CompositionScopeDefinition, MEF 2.0 provides much greater flexibility in terms of managing the scope and creation of parts. It should be noted that the MEF team considers that CompositionScopeDefinition is orientated more toward advanced desktop application solutions.

Diagnostic Improvements

The last thing you want in an application that provides third-party extensibility is for it to go down in flames because of composition problems. To ensure this doesn’t happen, the approach MEF takes is to silently reject the failed part. This makes sense for third-party extensibility, but if MEF is being used purely for internal development, it can be an issue when trying to debug an issue. To expose composition exceptions in the new version of MEF, you can now define a CompostionOptions.DisableSilentRejection flag in the CompositionContainer’s constructor that raises an exception when composition errors occur:

var container = new CompositionContainer(catalog,
CompositionOptions.DisableSilentRejection);

The current recommendation from the MEF team is that this flag should be set for non-third-party-extensible applications.

The other improvement in terms of diagnostics is with the actual exception message text. In the previous version of MEF, the exception messages were less than self-evident. With this version of MEF, the exceptions have been formatted so that the most important information is up front.

The Microsoft.Composition Package

In November 2011, the MEF team developed and released an experimental NuGet package for integrating MEF with MVC. Though the MVC integration has been dropped from this release (see the section Missing in Action), the web-optimized composition engine has been consolidated into the Microsoft.Composition package.

This NuGet package has been designed specifically for Windows 8 and web applications to not only provide a lightweight composition engine but also to recognize the fact that the scenarios for using MEF differ from those of desktop applications. For example, third-party extensibility is not really a requirement for Metro apps, but application decoupling and implementing patterns like MVVM are.

There are some differences between this implementation of MEF and the “full” version in the .NET Framework. A full list of these can be found at found at http://mef.codeplex.com/documentation, but some of the key ones include the following:

  • A different namespace is used. MEF for Windows 8/web apps uses the namespace System.Composition as opposed to System.ComponentModel.Composition.
  • Import/export visibility. In order for parts to be composed, all import and export members must be publicly visible.
  • Field exports and imports are not supported.
  • Parts are nonshared by default. If a part needs to be shared, it needs to be decorated with the [Shared] attribute.
  • The Shared() attribute accepts a simple scope name parameter to control sharing, for example, Shared("HttpRequest"). This works in conjunction with ExportFactory<T> and the SharingBoundary attribute.
  • Catalogs are not used. Instead, assemblies and part types are added to a ContainerConfiguration from which a container is created and exports can be requested or imports satisfied.
  • There is no RegistrationBuilder. For Windows 8 apps, the equivalent functionality is provided with the ConventionBuilder class.

Most of these points are self-explanatory, but let’s have a look at the last two to see what this means in terms of coding. (Note that the following explanation will be in terms of Windows 8 applications but should be equally applicable to web apps.)

Using the ContainerConfiguration

Rather than using catalogs, assemblies and part types are added to a ContainerConfiguration from which a container can be created to resolve imports and exports. The reasoning behind this is that support for open, extensible applications is not required for Windows 8 apps so the process has been simplified. The following code should give you a rough idea of how this works:

public interface IDatabaseConnection{}
interface IMessageHandler
{
  IDatabaseConnection DbConnection { get; }
}
[Export(typeof(IDatabaseConnection))]
public class FakeDbConnection: IDatabaseConnection{}

[Export(typeof(IMessageHandler))]
public class SimpleMessageHandler : IMessageHandler
{
  public IDatabaseConnection DbConnection { get; private set; }
  [ImportingConstructor]
  public SimpleMessageHandler(IDatabaseConnection dbConnection)
  {
    this.DbConnection = dbConnection;
  }
}

class MessageFactory
{
  [Import]
  public IMessageHandler MessageHandler { get; private set; }
  public MessageFactory()
  {
    var configuration = new ContainerConfiguration()
    .WithAssembly(typeof(App).GetTypeInfo().Assembly);

    using (var container = configuration.CreateContainer())
    {
         container.SatisfyImports(this);
        //alternatively could call GetExport
        // MessageHandler = container.GetExport<IMessageHandler>();
    }
  }
}

Using the ConventionBuilder

As mentioned earlier in this chapter, one of the new features in MEF for .NET 4.5 is the RegistrationBuilder, which provides the means of defining exports and imports using convention rather than attributes. This same functionality is available for Windows 8 apps but is provided through the ConventionBuilder class. If we take the previous code for the MessageFactory, we can rewrite it to use the ConventionBuilder:

class MessageFactory
{
  [Import]
  public IMessageHandler MessageHandler { get; private set; }
  public MessageFactory()
  {
    var conventionBuilder = new ConventionBuilder();
    conventionBuilder.ForType< FakeDbConnection>()
                     .Export<IDatabaseConnection>();
    conventionBuilder.ForTypesDerivedFrom<IMessageHandler>()
                     .Export<IMessageHandler>();
    var configuration = new ContainerConfiguration()
            .WithAssembly(typeof(App).GetTypeInfo().Assembly)
            .WithDefaultConventions(conventionBuilder);
    using (var container = configuration.CreateContainer())
    {
     try
     {
      MessageHandler = container.GetExport<IMessageHandler>();
     }
     catch (Exception ex)
     {
      //Handle the exception
     }
    }
  }
}

Though it may seem a little odd to use different names for classes that basically provide the same functionality, the MEF team foresees the ConventionBuilder class evolving along its own path and retaining the RegristationBuilder name would lead to confusion down the track.

To add the NuGet package to your solution, open the NuGet console window and enter the following command.

Install-Package Microsoft.Composition

Missing in Action

Though initially released as an experimental package via NuGet, as mentioned earlier, MEF integration for ASP.NET MVC won’t be released on the same time line as the core package. The MEF team is actively working on straightening out the MVC story, but at this point there is no indication what final shape it will take. The source code for this has been migrated to use the new Microsoft.Composition package and is available on the Codeplex site (http://mef.codeplex.com) if you are interested in having a look at it (in the source code, look for oobdemoMicrosoft.Composition.Demos.Web.Mvc).

Conclusion

With this version of MEF, the team has extended the scenarios that can be covered by the framework without losing sight of its primary function. The inclusion of a convention-based programming model and finer control over composition scope has introduced a degree of flexibility that, for some developers, was a stumbling block with the previous version. Finally, the targeting of different application models such as Windows 8 and web apps should see some interesting developments in the future.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.183.210