In the previous post I have described how to setup interception using the InterceptingCatalog and InterceptionConfiguration class. Just to remind you, the interception allows you to take control over the exported instances thanks to the IExportedValueInterceptor interface. Although this is the primary functionality, there is other bunch of stuff possible to do with the catalog. In this post I am going to show how to enable open-generics support for MEF using both the InterceptingCatalog and the decorating GenericCatalog. I will then introduce the IExportHandler interface which gives the ability to do some nice filtering based on a given criteria as well as to add new exports on the fly. The open generics support implementation is based on the on the fly export creation (thanks to the IExportHandler interface) as well as thing called part rewriting. Keep reading to find out more!
The problem
One of the biggest problem people complain about is the lack of open-generics support in MEF. You can read about this issue here, here and here (I advise you see these entries!). The canonical example folks use concerns IRepository<T>. Imagine the following code:
public interface IRepository<T>
{
T Get(int id);
void Save(T instance);
}
[Export(typeof(IRepository<>))]
public class Repository<T> : IRepository<T>
{
public T Get(int id)
{
return (...);
}
public void Save(T instance)
{
Console.WriteLine("Saving {0} instance.", instance.GetType().Name);
}
}
/// <summary>
/// Fake customer.
/// </summary>
public class Customer { }
[Export]
public class CustomerViewModel
{
[Import]
public IRepository<Customer> Repository { get; set; }
}
So what happens here ? We simply export a generic implementation of the generic interface IRepository<T> with an open-generic contract type, and we import a closed-generic part based on the open generic contract. Unfortunately, this isn’t supported out of the box in MEF. However, thanks to MefContrib, it is!
How can I get it ?
Open generics support shipped initially in the MefContrib 1.0 release. However, it had limited capabilities as the open generics worked only when generic types were exported using the InheritedExport attribute. To get the updated version, which enables to do things like the above, you have to download MefContrib repo and compile the sources yourself. Updated samples, including those presented here, are available in the MefContrib-Samples repo. Go and get them =)
Container setup
There are two ways of enabling open generics support. You can either use more verbose syntax using the InterceptingCatalog, or you can leverage the GenericCatalog. The GenericCatalog is noting more than a convenient decorating catalog which internally uses the InterceptingCatalog.
The first thing to do is to provide mapping between open generic interface and its implementation. This step is mandatory since the underlying infrastructure requires to produce export based on the concrete implementation. This export is produced on the fly, which means it is not contained in any composable catalog, but is created when it is needed. To map the contract type to its implementation you can either implement IGenericContractRegistry interface or inherit the GenericContractRegistryBase class. The implementation which supports the above example is presented below.
[Export(typeof(IGenericContractRegistry))]
public class MyGenericContractRegistry : GenericContractRegistryBase
{
protected override void Initialize()
{
Register(typeof(IRepository<>), typeof(Repository<>));
}
}
Important: the mapping is required only when you export the generic class with explicitly given contract type (like in the example). If you export generic class with its default contract type, no mapping is required!
Next is what you have probably expected – the catalog setup.
// Create source catalogAfter creating the interception configuration, we add the GenericExportHandler instance which is responsible for creating closed generic parts. This class implements the IExportHandler interface, which I will introduce in a moment. Meanwhile, let’s see the less verbose and cleaner setup routine.
var typeCatalog = new TypeCatalog(typeof(CustomerViewModel), typeof(MyGenericContractRegistry));
// Create the interception configuration and add support for open generics
var cfg = new InterceptionConfiguration()
.AddHandler(new GenericExportHandler());
// Create the InterceptingCatalog and pass the configuration
var interceptingCatalog = new InterceptingCatalog(typeCatalog, cfg);
// Create the container
var container = new CompositionContainer(interceptingCatalog);
// Get the repository
var repository = container.GetExportedValue<CustomerViewModel>().Repository;
// Create source catalog
var typeCatalog = new TypeCatalog(typeof(CustomerViewModel));
// Create catalog which supports open-generics, pass in the registry
var genericCatalog = new GenericCatalog(new MyGenericContractRegistry());
// Aggregate both catalogs
var aggregateCatalog = new AggregateCatalog(typeCatalog, genericCatalog);
// Create the container
var container = new CompositionContainer(aggregateCatalog);
// Get the repository
var repository = container.GetExportedValue<CustomerViewModel>().Repository;
Instead of creating the InterceptingCatalog, the GenericCatalog is created. This concludes how to setup MEF to support open generics. Next sections explain how stuff works, without digging into implementation details though. If you are curious, keep reading!
How does it work ?You are maybe asking yourself – given the export definition below – how does MEF know that when importing say IRepository<Order>, it should inject Repository<Order> ?!
[Export(typeof(IRepository<>))]
public class Repository<T> : IRepository<T>
{
}
In fact MEF only knows there is an export Repository<T> which is NOT the required one! And this is not the only question! Attentive reader will also observe the the contract type (which is a string) of the given export will be ‘SomeCleverNamespace.IRepository()’ which is an open generic. But the import’s contract type will be ‘SomeCleverNamespace.IRepository(SomeOtherCleverNamespace.Order)’ which is a closed generic. So even though MEF somehow knew the export Repository<Order>, the contract types wouldn’t match resulting in ImportCardinalityMismatchException.
The answer to the first question is on the fly export creation. Earlier I have introduced the concept of mapping contract type to its implementation(s). So when we import IRepository<>, MEF knows its implementation is Repository<>. But of course we import closed generic, like I said IReposiotry<Order>, so MEF knows the implementing type is Repository<Order>. Of course there is no export Repository<Order>, only Repository<T>. So it have to be introduced in runtime! This is where IExportHandler comes to play. The open generics support is all implemented in the GenericExportHandler class which is responsible for creating closed generic exports on the fly.
Finally, the answer to the second question is part rewriting. When we create Repository<Order> export on the fly (I mean in runtime), the export’s contract type will remain ‘SomeCleverNamespace.IRepository()’. So we have to rewrite the part so that the contract type and optionally contract name will be set properly. To do the part rewriting, special catalog is used – GenericTypeCatalog. It accepts two types – the concrete type being exported (e.g. Repository<Order>) and the open-generic contract type (e.g. IRepository<>). What the catalog does in its internals is to rewrite all open-generic exports which match the contract type to be closed-generic. In our example, the contract type will be rewritten from ‘SomeCleverNamespace.IRepository()’ to ‘SomeCleverNamespace.IRepository(SomeOtherCleverNamespace.Order)’. Same for contract name.
Meet the IExportHandler interface
The purpose of this interface is to filter out exports based on any criteria you want, as well as to dynamically create new exports. Let’s have a look at the IExportHandler signature.public interface IExportHandler
{
void Initialize(ComposablePartCatalog interceptedCatalog);
IEnumerable<Tuple<ComposablePartDefinition, ExportDefinition>>
GetExports(
ImportDefinition definition,
IEnumerable<Tuple<ComposablePartDefinition,ExportDefinition>> exports);
}
Wow! This looks complicated! Hopefully, it is not :) The initialize method gets called once when the export handler is initialized. Within this method you get a reference to the catalog being intercepted. The main player here is the GetExports method which has the exact same signature as the GetExports method in the ComposablePartCatalog class. It is called whenever an InterceptingCatalog is asked for exports which match the given import. The import definition is passed as the first argument. The second argument is a collection of matching export definitions along with theirs ComposablePartDefinition instances. The initial collection is the collection of exports from the intercepted catalog. Because the method returns the collection of matching exports, we can filter out the original exports we don’t want to show up, or what is more interesting, we can add our own export definitions!
Known limitations
Although support for open-generics in MefContrib is pretty amazing, it is not perfect. The first and probably the biggest limitation is the lack of support for recomposition. The other problem is that type being imported is inferred from the language construct rather than the import itself. Consider the following two imports.
[Import]
public IRepository<Order> OrderRepository { get; set; }
[Import(typeof(IRepository<Order>))]
public object OrderRepository { get; set; }
Both imports are perfectly legal. But only the former works as expected. This is because the type being imported is inferred from the property’s type, which is IRepository<Order>. In the latter example, the contract type is explicitly given in the Import attribute, but the property’s type is object, hence the inferred type will be object, which is not the case. You may want to know why is that. As you probably know, the import in MEF is represented by the ImportDefinition class. Unfortunately, it is NOT possible to get the Type instance representing the import from the ImportDefinition instance, only the defining construct (like property, field, etc.) and only for those ImportDefinition instances created by MEF’s Reflection Model.
ConclusionIn this post I have presented MefContrib’s approach to enable open-generic type composition. Hope you like it!