The title of today’s post might sound a little bit provoking, as originally MEF and StructureMap are not meant for the same use cases. However, as there is a great deal of overlapping functionality in StructureMap over MEF (and chances are high one is using an IoC framework anyway), it makes sense to check if we can reduce our arsenal of tools and libraries by one.
When we recently switched to StructureMap as our IoC container we did some testing of what is possible with the library and how it could be utilised best.
And when we came across the Registry.Scan()
method with its support to look for arbitrary assemblies in the file system something looked very familiar to our MEF extensions structure. Just much easier – so we had a look …
With MEF we normally have to define a catalogue that can include any assembly like the calling assembly (via new AssemblyCatalog(Assembly.GetCallingAssembly())
) and/or assemblies from an arbitrary (extensions) path (via new DirectoryCatalog(extensionsFolder)
). With StructureMap things are equally easy:
We first set up a Container
and add all required assemblies and paths to it:
var container = new Container(map => { map.Scan(scanner => { scanner.TheCallingAssembly(); scanner.AssembliesFromPath(extensionsFolder); }); });
As one can see there is no need to build a separate AggregateCatalog
. Everything can be configured fluenty inside the Scan
method.
While MEF depends on explicit annotations (like [Export(typeof(IMefPlugin))]
) to declare an extension and furthermore declaring a variable where to import the extensions into our application, StructureMap can do this without any annotations (as there is some arguing that an explicit annotation would violate the iOC principle). But this means we have to explicitly tell StructureMap which types we actually want to import. The default convention of StructureMap is to map IName to Name, but this is not sufficuent in our case, so we can define our own mapping convention. The following registration convention shows how to inject all types that implement an interface of type Type
:
public class MefRegistrationConvention : IRegistrationConvention { public void ScanTypes(TypeSet types, Registry registry) { types.FindTypes(TypeClassification.Concretes | TypeClassification.Closed) .Where(type => MefLoader.MefLoader.Type.IsAssignableFrom(type)) .ToList() .ForEach(type => { registry.For(MefLoader.MefLoader.Type).Use(type) .Singleton(); }); } }
Note that we specified .Singleton()
in our mapping to return always the same instance (just as MEF does). We then specify this convention as part of our scanner configuration:
scanner.Convention<MefRegistrationConvention>();
To actually get all instances for a specific interface we just have to issue:
container.GetAllInstances<T>()
One of the cool features of StructureMap is that it supports diagnostic features that let us determine what injections were actually performed (and from which assemblies). All it takes to get this information is to call the following lines:
Trace.WriteLine(container.WhatDoIHave()); Trace.WriteLine(container.WhatDidIScan());
The output of these diagnostics methods can further be tweaked: WhatDoIHave, WhatDidIScan. So no more reasoning if and why MEF did not import our extensions …
A sample output of these methods could look similar to this:
===================================================================================================================================================================================== PluginType Namespace Lifecycle Description Name ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- IMefPlugin biz.dfch.CS.Examples.DI.StructureMap.Public.MefLoader Singleton biz.dfch.CS.Examples.DI.StructureMap.Extensions.MefLoader.SomeMefPlugin (Default) Singleton biz.dfch.CS.Examples.DI.StructureMap.MefLoader.SomeMefPlugin Singleton biz.dfch.CS.Examples.DI.StructureMap.Extensions.MefLoader.AnotherMefPlugin ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Lazy<T> System Transient Open Generic Template for Func<> (Default) ===================================================================================================================================================================================== All Scanners ================================================================ Scanner #1 Assemblies ---------- * biz.dfch.CS.Examples.DI.StructureMap, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null * biz.dfch.CS.Examples.DI.StructureMap.Extensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null * biz.dfch.CS.Examples.DI.StructureMap.Public, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null Conventions -------- * biz.dfch.CS.Examples.DI.StructureMap.IoC.Conventions.MefRegistrationConvention
There we can see that StructureMap found three implementations for IMefPlugin
:
- biz.dfch.CS.Examples.DI.StructureMap.MefLoader.SomeMefPlugin
- biz.dfch.CS.Examples.DI.StructureMap.Extensions.MefLoader.SomeMefPlugin
- biz.dfch.CS.Examples.DI.StructureMap.Extensions.MefLoader.AnotherMefPlugin
The last two instances are taken from an external assembly.
Finally importing any attributes from our extensions (that in MEF we would normally annotate with ExportMetadata
] is pretty straighforward (if we really needed them) using reflection or a mapper class. However we find it seldomly necessary to supply static information for an extension, but instead much more use dynamic configuration (via app.config
or similar), which in turn can easily be solved by ConfigurationSection
classes or yet another IoC mapping.
Certainly MEF is a built-in feature of .NET that does not have any dependencies and StructureMap is yet another library. However in our case it perfectly makes sense as we are using an IoC container anyway (that gives us much more flexibility) and which allows us to use the same syntax and semantics. Plus, the future of MEF somehow seems uncertain (and the status of MEF2 not being very clear).
The complete code of the examples above can be found in MefLoader
class from our biz.dfch.CS.Examples.DI
repository.