My company just began working on a large, modular WPF project. I’ve been doing some research and recently took some time to dig into the Managed Extensibility Framework (MEF). I’m quite impressed with the direction that the project is going. MEF is going to fulfill our modularity needs quite well.
At the same time, I have refactored Caliburn to use the CommonServiceLocator that P&P released last year. Caliburn is a loosely coupled set of UI services and requires dependency injection to work. I have IServiceLocator implementations of Windsor, StructureMap, Unity and Spring.NET. Many of these implementations are based on the adapter code that can be found on the CommonServiceLocator site. Interestingly, there isn’t an implementation of IServiceLocator for MEF.
As my current project needs MEF to solve modularity concerns and Caliburn to solve UI architecture issues, I spent some time today writing an IServiceLocator adapter for MEF. It turned out not to be too difficult, but the code is not pretty. This stems from the fact that MEF’s CompositionContainer is generics based, but ServiceLocatorImplBase uses Type instances. I had to resort to using reflection to interact with the underlying container. I’m going to post the code below so that you can see how to do this sort of thing, but also in hopes that someone like Glenn can correct any stupid stuff ;) (I was hoping there is a non-reflection-based mechanism that I just didn’t see.)
In addition to implementing IServiceLocator, Caliburn needs a way to inform the container of its own components. In Caliburn you do this by either implementing IConfigurator or by providing a delegate which can take Caliburn’s ComponentInfo and register the services appropriately with the container. This is where the real MEF challenge comes in. MEF is not exactly a DI container, and thus doesn’t have the typical registration mechanism that you might expect. I wasn’t really sure what the best way to solve this problem was, so I implemented my own ComposablePart. Below is the entire MEFAdapter which implements both IServiceLocator and IConfigurator. I would appreciate feedback from any MEF experts or members of the MEF team. Much thanks in advance!
MEFAdapter
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using Caliburn.Core;
using Microsoft.Practices.ServiceLocation;
namespace Caliburn.MEF
{
public class MEFAdapter : ServiceLocatorImplBase, IConfigurator
{
private readonly CompositionContainer _container;
private readonly MethodInfo _getExportedObjectByType;
private readonly MethodInfo _getExportedObjectByTypeAndKey;
private readonly MethodInfo _getExportedObjectsByType;
public MEFAdapter(CompositionContainer container)
{
_container = container;
_getExportedObjectByType = _container.GetType().GetMethod("GetExportedObject", Type.EmptyTypes);
_getExportedObjectByTypeAndKey = _container.GetType().GetMethod("GetExportedObject", new[] { typeof(string) });
_getExportedObjectsByType = _container.GetType().GetMethod("GetExportedObjects", Type.EmptyTypes);
}
protected override object DoGetInstance(Type serviceType, string key)
{
MethodInfo genericMethod;
if(!string.IsNullOrEmpty(key))
{
genericMethod = _getExportedObjectByTypeAndKey.MakeGenericMethod(serviceType);
return genericMethod.Invoke(_container, new[] { key });
}
genericMethod = _getExportedObjectByType.MakeGenericMethod(serviceType);
return genericMethod.Invoke(_container, null);
}
protected override IEnumerable<object> DoGetAllInstances(Type serviceType)
{
MethodInfo genericMethod = _getExportedObjectsByType.MakeGenericMethod(serviceType);
var results = (IEnumerable)genericMethod.Invoke(_container, null);
foreach(var result in results)
{
yield return result;
}
}
public void ConfigureWith(IEnumerable<ComponentInfo> components)
{
var batch = new CompositionBatch();
foreach(var componentInfo in components)
{
batch.AddPart(new ComponentPart(componentInfo));
}
_container.Compose(batch);
}
}
}
ComponentPart
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition.Primitives;
using System.Linq;
using System.Reflection;
using Caliburn.Core;
namespace Caliburn.MEF
{
public class ComponentPart : ComposablePart
{
private readonly ComponentInfo _info;
private readonly List<ImportDefinition> _imports = new List<ImportDefinition>();
private readonly ExportDefinition[] _exports;
private readonly Dictionary<ImportDefinition, Export> _satisfiedImports = new Dictionary<ImportDefinition, Export>();
private object _cachedInstance;
private readonly ConstructorInfo _greedyConstructor;
public ComponentPart(ComponentInfo info)
{
_info = info;
_greedyConstructor = (from c in info.Implementation.GetConstructors()
orderby c.GetParameters().Length descending
select c).FirstOrDefault();
if (_greedyConstructor != null)
{
foreach (var parameterInfo in _greedyConstructor.GetParameters())
{
var parameterType = parameterInfo.ParameterType;
var import = new ImportDefinition(
def => def.ContractName == parameterType.FullName,
ImportCardinality.ExactlyOne,
false,
true
);
_imports.Add(import);
}
}
string contractName = info.Key is string ? info.Key.ToString() : ((Type)info.Key).FullName;
var export = new ExportDefinition(
contractName,
null
);
_exports = new[] {export};
}
public override object GetExportedObject(ExportDefinition definition)
{
if (_info.Lifetime == ComponentLifetime.PerRequest)
return CreateInstance(definition);
if(_cachedInstance == null)
_cachedInstance = CreateInstance(definition);
return _cachedInstance;
}
private object CreateInstance(ExportDefinition definition)
{
var args = new List<object>();
if(_greedyConstructor != null)
{
foreach(var parameterInfo in _greedyConstructor.GetParameters())
{
var arg = (from export in _satisfiedImports.Values
where export.Definition.ContractName == parameterInfo.ParameterType.FullName
select export).FirstOrDefault();
args.Add(arg.GetExportedObject());
}
}
return args.Count > 0
? Activator.CreateInstance(_info.Implementation, args.ToArray())
: Activator.CreateInstance(_info.Implementation);
}
public override void SetImport(ImportDefinition definition, IEnumerable<Export> exports)
{
_satisfiedImports[definition] = exports.FirstOrDefault();
}
public override IEnumerable<ExportDefinition> ExportDefinitions
{
get { return _exports; }
}
public override IEnumerable<ImportDefinition> ImportDefinitions
{
get { return _imports; }
}
}
}
Posted
02-04-2009 5:12 PM
by
Rob Eisenberg