- Download ConsoleMef - 126.14 KB
- Download Mef - 102.61 KB
Introduction
MEF was probably one of the best additions to make its appearance into the .net framework. It brought about a very simplistic way of providing loosely coupled architecture to your application, by way of an attribute driven design. That being said, I can certainly go on about the many advantages MEF provides, but many people have already written extensively about all its strengths. Instead I’m going to rather discuss how we can harness the power of MEF and extend it by way of generics. We all use generics to make our applications code…well more generic, so the question is why Inversion of control containers can’t support it as well? Well lucky for us MEF can. But not directly out of the box though, you have to go about setting up a few things first to get it to fully support generic exports. Lucky for all the beginners out there, I’m going to show you how to do this.
Getting Started
Let’s kick things off, firstly those of you who haven’t used MEF and are thinking about integrating this technology into your application. Please take a look at the following links below to give you some clarity on this article that will follow.
- http://mef.codeplex.com/
- http://archive.msdn.microsoft.com/mef
- http://msdn.microsoft.com/en-us/library/dd460648.aspx
- http://msdn.microsoft.com/en-us/magazine/ee291628.aspx
Design Overview
Once you’ve caught up to speed. We can get to the good part and discuss how all this works exactly. MEF supports certain types of catalogs as you know for example we get the
AggregateCatalog
,DirectoryCatalog
and finally theAssemblyCatalog
. Which we all know is highly useful in part discovery by adding assemblies anyway you see fit. But it doesn’t help when it comes to generics. It’s simply because the current catalogs seem a bit lack luster when it comes to finding generic exports and knowing what to do with them. That’s where the brilliant developers’ from the MEF Contrib team come into the picture. They’ve developed a catalog that helps to map generic exports to their concrete types, so you can later import them at run-time like any other import. There is one slight problem though on their page on codeplex. And that is the documentation doesn’t exactly give you the slightest idea how to set it up in a simple way. Some might find it a bit confusing. If you are however a diligent researcher or a google wizard you can certainly find the solution. But this article isn’t directed at solving a problem, which has been done already. This article is meant to save you time as well as help you set it all up. Just if you are wondering the Mefcontrib dlls and all the files you need are supplied in the source code above. A quick note if you do want the latest copy of the library you can go to http://mefcontrib.codeplex.com/. Let’s begin!Referencing the References
Above you can see all the necessary dependencies for generic exports to be used. The referenced assemblies are as follows:
MefContrib.dll , System.Composition.dll
and my custom dllMef.Extended.Tools
.Using the code
Let’s start with the coding aspect of this guide. The simple part of it all setting up the Catalog as well as the container for our generic exports. Firstly in your applications startup add the following references you added to your project earlier, so that you can have access to the container, catalog, registry etc.
Collapse | Copy Codeusing System.ComponentModel.Composition.Hosting; using MefContrib.Hosting.Generics; using Mef.Extended.Tools.Generics; class Program { static void Main(string[] args) { string assemblyPath = "ConsoleMef"; //my example assembly //declare mefcontainer and add genericCatalog var genericRegistry = new GenericContractRegistry( new TypeResolver(x => Path.GetFileName(x).StartsWith(assemblyPath))); var catalog = new GenericCatalog(genericRegistry); var container = new CompositionContainer(catalog); } }
As I’ve showed above, you start by instantiating a
GenericContractRegistry
which takes my own custom classTypeResolver
as an argument. The TypeResolver’s constructor accepts a lambda expression is used to filter all the assemblies that contain exports in them. TheGenericContractRegistry
is then passed in as an argument to an instance of theGenericCatalog
. Which you’ll see shortly does all that magic I was talking about earlier. The container then accepts the catalog as its argument to then compose all exports that are available to later be imported. Those of you who are a little lost about my custom type, well don’t stress they will be discussed in depth shortly.Applying some Exportation
Now that the container is setup to discover exports. You now need to supply the exports that are going to be used throughout the life time of your application. Adding exports is exactly the same as what you’ve been doing with all your previous MEF implementations. it’s the same as before. For example
Collapse | Copy Code[InheritedExport] public interface IRepository<T> where T : class { string GetName(T obj); }
One thing to note at time of writing my implementation only supports the use of
InheritedExportAttribute
. The reason for this is it makes more sense to mark an interface or abstract class with an attribute to be exported rather than the implemented object, purely because of the fact if you on a big development team like me, a simple thing like marking a class with an attribute can easily be forgotten. Note this will also work for generic abstract classes.Here’s an example repository that Implements our exported interface. Note both have to be generic types.
Collapse | Copy Codepublic class PersonRepository<T> : IRepository<t> where T : class { public string GetName(T obj) { Console.WriteLine("calling GetName"); Console.WriteLine(obj.ToString()); return obj.ToString(); } } class Program { [Import] public IRepository<Person> Repository { get; set; } static void Main(string[] args) { string assemblyPath = "ConsoleMef"; //my example assembly //declare mefcontainer and add genericCatalog var genericRegistry = new GenericContractRegistry( new TypeResolver(x => Path.GetFileName(x).StartsWith(assemblyPath))); var catalog = new GenericCatalog(genericRegistry); var container = new CompositionContainer(catalog); new Program().Run(container); Console.ReadKey(); } void Run(CompositionContainer container) { container.ComposeParts(this); RunTest(); } void RunTest() { var p = new Person { FirstName = "Dean", LastName = "Oliver" }; Repository.GetName(p); } }
Show Me Some Generic Magic
And there we go it’s all working together in harmony, a working implementation of a generic
Export/Import
through mef. This has all been done with marginal effort and even less code. After all one of mef’s biggest strengths is its simplicity.Now let’s examine under the hood what’s driving this little generic engine.
Under The Hood
There are many facets that contribute to mef resolving generic types for imports, but to keep with the theme of simplicity. Let me start by saying that it’s really just one big mapping system that assigns an interface to its implemented concrete type.
Collapse | Copy Code/// <summary> /// Maps concrete generic types to interface generic types. /// [Export(typeof(IGenericContractRegistry))] public class GenericContractRegistry : GenericContractRegistryBase { private readonly ITypeResolver _resolver; public GenericContractRegistry(ITypeResolver resolver) { _resolver = resolver; } protected override void Initialize() { RegisterAll(i => i.IsGenericType && i.GetCustomAttributes(false) .Any(x => x.GetType() == typeof(InheritedExportAttribute))); //Register(typeof(IItemObservable<>), typeof(ItemObservableCollection<>)); //Register(typeof(ICommandInvoker<>), typeof(CommandInvoker<>)); } private void RegisterAll(Func<type,> filter) { var assemblyResolver = new AssemblyResolver(_resolver.AssemblyFilter); var asm = assemblyResolver.Assemblies.GetMappings(x => _resolver.Get(x), filter) foreach (var map in asm) Register(map.Value, map.Key); } }
This class is the core to mef being able to support generics. Think of it as one big generic gps. What it does is map an interface that has the
InheritedExportAttribute
applied to it, to a concrete implementation of that specific type. It searches through the assemblies that theTypeResolver
defines based on particular criteria. And it adds it to a custom collection calledAssemblyList
. TheAssemblyResolver
calls this list to get a particular mapping from each assembly in the collection and then adds it to the register so that MEF is aware of a new export in theGenericCatalog
. You may have noticed in the MefContrib documentation they tell you to setup a mapping like what I have commented out above. The problem with setting up a mapping this way is it continually violates the open/closed principle. Which stipulates a class may be open for extension but closed for modification. I see this as continually modifying a class to support more mappings. So I thought of a more dynamic way of doing this as I’ve explained above. Mef prides itself on part discovery, for example itsDirectoryCatalog
looks for parts in directories. So the question is why we can’t be just as dynamic for ourGenericCatalog
. It sometimes seems the best solution is one that is more dynamic more often than not. That was my rationale behind that little change. Let’s now turn our attention to retrieving the assemblies that contain the mappings.Off To the Libraries
Collapse | Copy Codepublic class AssemblyResolver { public AssemblyResolver(params Assembly[] assemblies) : this(() => assemblies) { } public AssemblyResolver(Func<Assembly[]> getList) { Assemblies = new AssemblyList(getList()); } public AssemblyResolver(Func<string,> searchFilter = null) { Assemblies = new AssemblyList(GetAssemblies(searchFilter)); } public AssemblyList Assemblies { get; set; } public static Assembly[] GetReferencedAssemblies() { return AppDomain.CurrentDomain.GetAssemblies(); } public Assembly[] GetAssemblies([Optional]Func<string,> searchFilter) { var assemblies = Directory .GetFileSystemEntries (AppDomain.CurrentDomain.BaseDirectory, "*.dll", SearchOption.AllDirectories); var currentAssemblies = searchFilter == null ? assemblies : assemblies.Where(searchFilter).ToArray(); var asm = currentAssemblies.Select(Assembly.LoadFrom); return asm.Union(new[] { Assembly.GetExecutingAssembly() }).ToArray(); } }
The
AssemblyResolver
class gets all the assemblies that are referenced in the current application as well as the current applications executing assembly. This allows theGenericContractRegistry
to do an extensive mapping of all generic exports across all assemblies involved. The search is narrowed somewhat by the delegate that defines the search criteria for each assembly, so only the assemblies that meet that particular criteria are retrieved.Collapse | Copy Codepublic Dictionary<Type, Type> GetMappings(Func<type,> resolver, Func<type,> filter) { return this.Select(assembly => (from c in assembly.GetTypes() from i in c.GetInterfaces() where filter(i) select new { ClassType = c.GetInterfaceMap(i).TargetType, InterfaceType = resolver(i) })) .SelectMany(genericExportList => genericExportList) .ToDictionary(x => x.ClassType, z => z.InterfaceType); }
LINQ Marks the spot
The true marvels of modern c#, where would we be without linq? It makes querying through reflection an absolute treat. The
GetMapping()
method creates a dictionary of class types as keys and interfaces that they implement as values. This simple query associates all interfaces to their correct implemenations. Above you can see theAssemblyList
collection that takes care of holding all the applications assemblies in a custom assembly collection.That's All Folks
I’m not quite done with you avid developers just yet. Sometimes complex things can be solved with simple ideas, what we achieved was a mef container that completely supports generics and conforms to the mef way of doing things. For those of you who enjoy even more of the Nitti gritty and want to understand how the
GenericCatalog
works to allow us to have generic exports, well then I suggest taking a look at this article: http://mefcontrib.codeplex.com/wikipage?title=Generic%20Catalog&referringTitle=Documentation%20%26%20Features Well it’s been an absolute blast to write this article, it is always easier when you love what you doing though. Hope you find something useful out of this article. Please remember to vote and comment if you don’t like something or a suggestion. At the end of the day criticism just helps you to better understand your flaws so you can come closer to perfecting them and making them your strengths.
MEF和泛型 MEF Generics
最新推荐文章于 2016-12-13 14:18:15 发布