In the last days of 2010 Mark Seemann announced a Christmas challenge concerning MEF. MEF uses attributes to export objects and inject them later where needed. The challenge was to find a way how to export objects and inject them in constructors without using attributes. Personally I don’t have much experience with MEF because I have mostly worked with other DI containers like autofac and Ninject. I like puzzles like this so I started experimenting. The first thought I had when I saw the chanllenge was about fluent MEF. I knew there was already such a project but I didn’t know its progress. So I started investigating MEF’s extensibility points.
MEF offers a few ways for extensibility. Each of them has its pros and cons so you have to decide which one fits best in your solution.
Using Export Providers
The simplest way to extend MEF is to create an export provider. You have to inherit from ExportProvider and implement its GetExportsCore method. This method provides a list of exports which satify an import definition. Here is a simple example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class ApplicationSettingExportProvider : ExportProvider { private readonly List<Export> _exports; public ApplicationSettingExportProvider(NameValueCollection settings) { _exports = new List<Export>(); foreach (string key in settings) { var metadata = new Dictionary<string, object>(); metadata.Add(CompositionConstants.ExportTypeIdentityMetadataName, typeof(string).FullName); var exportDefinition = new ExportDefinition(key, metadata); _exports.Add(new Export(exportDefinition, () => settings[key])); } } protected override IEnumerable<Export> GetExportsCore( ImportDefinition definition, AtomicComposition atomicComposition) { return _exports.Where(x => definition.IsConstraintSatisfiedBy(x.Definition)); } } |
Using ReflectionModelServices and AttributedModelServices
MEF provides static services which can help you create import and export dewfinitions. You need to create your own ComposablePartCatalog and override its Parts property. This property is of type IQueryable and provides a way to query all composable part definitions. Before proceeding to the example, let me explain you some definitions about MEF.
- ComposablePartCatalog is the core class, which encapsulates different part definitions for the system.
- ComposablePartDefinition defines and creates parts. It can be compared to what a class is in terms of OOP.
- ComposablePart is an instance of live executing software component. It can be compared to what an object is in terms of OOP. It contains dependencies about exports and imports related to it. For example, if you put an Export attribute to a class and ImportingConstructor attribute to its constructor, then you have a composable part definition which defines one export and one import.
- ExportDefinition comprises a contract name and metadata, which describes the export. This metadata can contain the identity of the type being exported and its creation policy (singleton or shared), for example.
- ImportDefinition describes exactly what exports are required to be injected to a field (or property)
For more details you can refer to this article explaining MEF’s architecture. So here is a simple example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
public class ConventionalCatalog : ComposablePartCatalog { private List<ComposablePartDefinition> _parts = new List<ComposablePartDefinition>(); public void RegisterType<TImplementation, TContract>() { var part = ReflectionModelServices.CreatePartDefinition( new Lazy<Type>(() => typeof(TImplementation)), false, new Lazy<IEnumerable<ImportDefinition>>(() => GetImportDefinitions(typeof(TImplementation))), new Lazy<IEnumerable<ExportDefinition>>(() => GetExportDefinitions(typeof(TImplementation), typeof(TContract))), new Lazy<IDictionary<string, object>>(() => new Dictionary<string, object>()), null); _parts.Add(part); } private ImportDefinition[] GetImportDefinitions(Type implementationType) { var constructors = implementationType.GetConstructors()[0]; var imports = new List<ImportDefinition>(); foreach (var param in constructors.GetParameters()) { imports.Add( ReflectionModelServices.CreateImportDefinition( new Lazy<ParameterInfo>(() => param), AttributedModelServices.GetContractName(param.ParameterType), AttributedModelServices.GetTypeIdentity(param.ParameterType), Enumerable.Empty<KeyValuePair<string,Type>>(), ImportCardinality.ExactlyOne, CreationPolicy.Any, null)); } return imports.ToArray(); } private ExportDefinition[] GetExportDefinitions(Type implementationType, Type contractType) { var lazyMember = new LazyMemberInfo(implementationType); var contracName = AttributedModelServices.GetContractName(contractType); var metadata = new Lazy<IDictionary<string, object>>(() => { var md = new Dictionary<string, object>(); md.Add(CompositionConstants.ExportTypeIdentityMetadataName, AttributedModelServices.GetTypeIdentity(contractType)); return md; }); return new ExportDefinition[] { ReflectionModelServices.CreateExportDefinition(lazyMember, contracName, metadata, null) }; } public override IQueryable<ComposablePartDefinition> Parts { get { return _parts.AsQueryable(); } } } |
Using Custom ComposablePartDefinition
The hardest way to extend MEF is to create everything yourself. However it lets you customize many things so it gives you a lot of flexibility. Basically, the example is the same as the previously having that you have to return your own objects instead of using MEF’s static services. You can download my solution to Mark’s challenge which uses this approach. I have created a small fluent MEF implementation which only solves the challenge. Of course it can be extended but I decided not to pay that much attention to it because there is already such a project (which btw is also not far developed).
I haven’t written the code for my examples myself so here are links to its source:
- http://codepaste.net/yadusn
- http://www.michaelfcollins3.me/2010/12/extending-mef-based-applications-with-export-providers/
And you can download my solution here.