MEF disguising to Castle Windsor
This post explore advance techniques of changing the default MEF discovery behavior
into Castle Windsor like behavior (Castle Windsor is used as test case and it’s not the main subject of this post).
The code for this post available here (it built upon MEF preview 5.)
Prerequisites
This post assume that you have some knowledge on MEF and some understanding on DI (dependency injection)
Previous knowledge on Castle Windsor (which is one of the most common DI implementation) is not needed.
you can read more on MEF at the following posts: MEF Introduction, http://delicious.com/bnaya/mef.
Background
Out of the box, MEF come with a few very useful discovery model that relay on attribute decoration.
those model includes TypeCatalog, AssemblyCatalog, and DirectoryCatalog.
Brief description about MEF architecture:
MEF layers
The MEF architecture has 3 layers of abstraction:
- Hosting responsible for the actual composition and the actual API for the MEF consumers
- Primitive is the abstraction of the MEF components but dose not implement
the actual discovery and matching model
- Model layer responsible for the actual discovery and matching model,
MEF come with attribute model out of the box
Custom model
Creating custom module involve with inheritance of the following classes:
- Catalog which responsible for discovery and matching strategy
- ComposablePartDefinition which holding imports and exports definition
and use as factory for ComposablePart
- ExportDefinition holding the information about the export parts
- ImportDefinition define constraint which filler the ExportDefinition that satisfy the import
- ComposablePart which is responsible for instantiating the concrete export part
About the code sample
The code sample taking Castle Windsor model as test case for custom MEF model.
The sample solution has 2 projects:
- CastleMEF project which implement the MEF model
- CastleConsole which consume the CastleMEF module, using Castle Windsor configuration
CastleConsole configuration
<castle>
<components>
<component id="Logger"
service="Bnaya.Samples.ILogger, CastleConsole"
type="Bnaya.Samples.ColsoleLogger, CastleConsole">
</component>
</components>
</castle>
We can see very simple Castle Windsor configuration that map ILogger to be instantiate with ConsoleLogger
Castle Windsor instantiation Vs. MEF instantiation
Instantiating ILogger using Castle Windsor:
IWindsorContainer castleContainer = new WindsorContainer (new XmlInterpreter ());
ILogger castleLogger = castleContainer.Resolve<ILogger> ();
Instantiating ILogger using MEF:
ILogger mefSimpleLogger = CastelCompositor.Resolve<ILogger> ();
As we can see the consumer get quite similar experience.
Diving into the custom MEF implementation
Catalog:
public class CastelCatalog: ComposablePartCatalog {
private IQueryable<ComposablePartDefinition> _queryableParts = null;
public override IQueryable<ComposablePartDefinition> Parts {
get {
var collection = new List<ComposablePartDefinition> ();
collection.Add (new CastelPartDefinition (…));
return this._queryableParts;
}
}
}
The catalog responsible for give IQueryable of ComposablePartDefinition
ComposablePartDefinition
public class CastelPartDefinition: ComposablePartDefinition {
private Dictionary<string, object> _mapping; // the mapping as the catalog resolve from the configuration
public CastelPartDefinition ( Dictionary<string, object> mapping ) {
_mapping = mapping;
}
public override ComposablePart CreatePart () {
return new CastelPart (this);
}
public override IEnumerable<ExportDefinition> ExportDefinitions {
get {
return from item in _mapping
select new CastelExportDefinition (item.Key, item.Value) as ExportDefinition;
}
}
public override IEnumerable<ImportDefinition> ImportDefinitions {
get {
return from item in _mapping
select new CastelImportDefinition (item.Key) as ImportDefinition;
}
}
}
ExportDefinition
public class CastelExportDefinition: ExportDefinition {
private IDictionary<string, object> _metadata;
public CastelExportDefinition ( string contractName, object instance )
: base (contractName, null) {
_metadata = new Dictionary<string, object> (base.Metadata);
_metadata.Add ("Instance", instance); // holding the actual instance
_metadata.Add ("ExportTypeIdentity", contractName); // won't work without it
}
public override IDictionary<string, object> Metadata {
get {
return _metadata;
}
}
}
Export definition responsible to supply the right instance information to the right contract
ImportDefinition
public class CastelImportDefinition: ImportDefinition {
public CastelImportDefinition ( string contract )
: base (e => e.ContractName == contract,
ImportCardinality.ExactlyOne, false, false) {
}
}
Import definition responsible to set the filtering which satisfy the import (e => e.ContractName == contract)
ComposablePart
public class CastelPart: ComposablePart {
private readonly CastelPartDefinition _def;
public CastelPart (CastelPartDefinition def) {
_def = def;
} public override object GetExportedObject ( ExportDefinition definition ){
if (!definition.Metadata.ContainsKey ("Instance"))
return null;
return definition.Metadata["Instance"];
}
}
The role of the composable part is to hand the concrete instance
Summary
MEF built upon abstraction layer (primitives) therefore we can replace the
out of the box attributed model with our own.
It can be useful in cases that we need to compose the MEF upon
runtime information like central configuration, cases where the
design-time attribute model does not fit.