• 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.

    Design Overview

    MEF和泛型 MEF Generics_generics

    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 the AssemblyCatalog. 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

    MEF和泛型 MEF Generics_documentation_02

    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 dll Mef.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.

    MEF和泛型 MEF Generics_class_03 Collapse | Copy Code
    using 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 class TypeResolver as an argument. The TypeResolver’s constructor accepts a lambda expression is used to filter all the assemblies that contain exports in them. The GenericContractRegistry is then passed in as an argument to an instance of the GenericCatalog. 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

    MEF和泛型 MEF Generics_class_03 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.

    MEF和泛型 MEF Generics_class_03 Collapse | Copy Code
    public 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

    MEF和泛型 MEF Generics_class_06

    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.

    MEF和泛型 MEF Generics_class_03 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 the TypeResolver defines based on particular criteria. And it adds it to a custom collection called AssemblyList. The AssemblyResolver 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 the GenericCatalog. 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 its DirectoryCatalog looks for parts in directories. So the question is why we can’t be just as dynamic for our GenericCatalog. 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

    MEF和泛型 MEF Generics_class_03 Collapse | Copy Code
    public 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 the GenericContractRegistry 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.

    MEF和泛型 MEF Generics_class_03 Collapse | Copy Code
    public 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 the AssemblyList 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.