Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

The Simplest Way to use MEF Fully Lazy DLL Loading

0.00/5 (No votes)
3 Mar 2014 1  
The Simplest way to (generically) use MEF fully lazy also for the Dll loading process.

Introduction

This article provides an easy a generic approach of working with MEF:

  1. The Simplest way to (generically) use MEF.
  2. How to use MEF fully lazy also for the Dll loading process.

Background

MEF stand for "Managed Extensibility Framework" - The Managed Extensibility Framework or MEF is a library for creating lightweight, extensible applications. It allows application developers to discover and use extensions with no configuration required. It also lets extension developers easily encapsulate code and avoid fragile hard dependencies. MEF not only allows extensions to be reused within applications, but across applications as well. Please see MSDN link.

There are many articles about MEF, although I get many questions about the subject from Architects/Dev. Managers/Colleague Lecturers/Students, about how to use MEF simply & generically without loading all the dlls at the same time.

MEF Loader generic logic

The Simplest way to use MEF is generically as a black box, the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

namespace MEF_Example.MEF
{

    /// <summary>
    /// MEFLoader class that responsible for :
    ///    The interface for all the MEF loading process, i.e. he is the black-box.
    ///    holding all the already imported object (for better performance) 
    ///    holding all the already exist importers (one for each type)
    /// Written by Shai Vashdi - Shai.Vashdi.Net@gmail.com - All rights reserved.
    /// </summary>
    public class MEFLoader
    {
        Dictionary<string, List<object>> importers = new Dictionary<string, List<object>>();

        public virtual ICollection<T> LoadByTag<T>(string path, string tag)
        {
            var importer = GetImporter<T>(path);

            return importer.LoadByMEF(path, tag);
        }

        protected MEFImporter<T> GetImporter<T>(string path)
        {
            var importerList = GetImporterList(path);

            var importer = importerList.OfType<MEFImporter<T>>().FirstOrDefault();

            if (importer == null)
            {
                importer = new MEFImporter<T>(path);
                importerList.Add(importer);

                //Write to Log:
                //UILogService.Instance.WriteToLog(E_ErrorSeverity.e_Information, "New MEFImporter was created for Path & Type" + path + importer.GetType().ToString());
            }

            return importer;
        }

        protected List<object> GetImporterList(string path)
        {
            if (importers.ContainsKey(path) == false)
                importers.Add(path, new List<object>());

            return importers[path];
        }

        public virtual ICollection<T> LoadByType<T>(string path)
        {
            return LoadByTag<T>(path, String.Empty);
        }
    }

    /// <summary>
    /// The imported objects metadata interface. i.e. the set of 
    /// properties we can filter by all the already imported objects.
    /// Written by Shai Vashdi - Shai.Vashdi.Net@gmail.com - All rights reserved.
    /// </summary>
    public interface IMetadata
    {
        string Name { get; }
    }

    /// <summary>
    /// Generic Class is responsible for MEF Import of certain type T.
    /// Written by Shai Vashdi - Shai.Vashdi.Net@gmail.com - All rights reserved.
    /// </summary>
    public class MEFImporter<T>
    {
        [ImportMany(AllowRecomposition = true)]
        public IEnumerable<Lazy<T, IMetadata>> imports { get; set; }

        MEFImporter()
        {
        }

        public MEFImporter(string path)
            : this()
        {
            directoryCatalog = new DirectoryCatalog(path);
        }

        protected DirectoryCatalog directoryCatalog = null;

        protected void DoImport(string path)
        {
            //An aggregate catalog that combines multiple catalogs
            var catalog = new AggregateCatalog();
            //Adds all the parts found in all assemblies in 
            //the same directory as the executing program
            catalog.Catalogs.Add(directoryCatalog);

            //Create the CompositionContainer with the parts in the catalog
            CompositionContainer container = new CompositionContainer(catalog);

            //Fill the imports of this object
            container.ComposeParts(this);
        }

        public ICollection<T> LoadByMEF(string path, string name)
        {
            var res = new List<T>();

            DoImport(path);
            //Test MEF
            //AppDomain MyDomain = AppDomain.CurrentDomain;
            //var AssembliesLoaded = MyDomain.GetAssemblies();

            foreach (Lazy<T, IMetadata> module in imports)
            {
                if (module.Metadata.Name == name || String.IsNullOrEmpty(name))
                {
                    res.Add(module.Value); //Will create an instance
                }
            }

            return res;
        }
    }
}
Colourised in 63ms
  • The MEFImporter Generic Class is responsible for MEF Import of certain type T
  • The IMetadata interface is the imported objects metadata interface. i.e. the set of properties we can filter by all the already imported objects.
  • And finally the MEFLoader class that responsible for :
    • The interface for all the MEF loading process, i.e. he is the black-box.
    • holding all the already imported object (for better performance)
    • holding all the already exist importers (one for each type)

The problem is: MEF loads All the DLLs at the “path” folder!

NOTE: By the use of Lazy<> at least he does not creates All the Objects fits our request.

Using the code

Q: How to use?

A: Example:

The basic idea of the example (WPF) application is:

3 button -> on click -> load the custom control that match the button -> load the custom control by MEF:

The Implementation, MainWindow.xaml:

<Window x:Class="MEF_Example.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <DockPanel LastChildFill="True">
            <StackPanel DockPanel.Dock="Top" >
                <Button Content="Present Blue Control" Background="Blue" Click="Button_Click" Tag="1"/>
                <Button Content="Present Green Control" Background="Green" Click="Button_Click" Tag="2"/>
                <Button Content="Present Yellow Control" Background="Yellow" Click="Button_Click" Tag="3"/>
            </StackPanel>
            <ContentPresenter x:Name="Worksapce" />
        </DockPanel>     
    </Grid>
</Window>
Colourised in 11ms

Code Behined, MainWindow.xaml.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using ControlInterface;

namespace MEF_Example
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// Written by Shai Vashdi - Shai.Vashdi.Net@gmail.com - All rights reserved.
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        MEF.MEFLoader m_Loader = new MEF.MEFLoader();

        string GetPathByName(string name)
        {
            var res = AppDomain.CurrentDomain.BaseDirectory + @"Controls\";

            return res;
        }

        UserControl GetControlByName<T>(string name)
        {
            UserControl res = null;

            res =  m_Loader.LoadByTag<T>(GetPathByName(name), name).FirstOrDefault() as UserControl;

            return res;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var btn = (e.OriginalSource as Button);

            if (btn == null && btn.Tag != null)
                return;
            int number = 0;
            Int32.TryParse(btn.Tag as string,out number);

            switch (number)
            {
                case 1:
                    //Present Blue Control (dll: ControlsLibrary1.dll):
                    this.Worksapce.Content = GetControlByName<IControl>("ControlsLibrary1.BlueControl");
                    break;
                case 2:
                    //Present Green Control (dll: ControlsLibrary1.dll):
                    this.Worksapce.Content = GetControlByName<IControl>("ControlsLibrary1.GreenControl");
                    break;
                case 3:
                    //Present Yellow Control (diffrent dll: ControlsLibrary2.dll):
                    this.Worksapce.Content = GetControlByName<IControl>("ControlsLibrary2.YellowControl");
                    break;
            }
        }
    }
}
Colourised in 47ms

The UserControl Classes (ControlsLibrary1.dll ):

BlueControl.xaml:

<UserControl x:Class="ControlsLibrary1.BlueControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid Background="Blue">           
    </Grid>
</UserControl>
Colourised in 4ms

BlueControl.xaml.cs:

namespace ControlsLibrary1
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    [Export(typeof(IControl))]
    [ExportMetadata("Name", "ControlsLibrary1.BlueControl")]
    public partial class BlueControl : UserControl, IControl
    {
        public BlueControl()
        {
            InitializeComponent();
        }
    }
}
Colourised in 7ms

GreenControl.xaml:

<UserControl x:Class="ControlsLibrary1.GreenControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid Background="Green">           
    </Grid>
</UserControl>
Colourised in 7ms

GreenControl.xaml.cs:

namespace ControlsLibrary1
{
    /// <summary>
    /// Interaction logic for GreenControl.xaml
    /// </summary>
    [Export(typeof(IControl))]
    [ExportMetadata("Name", "ControlsLibrary1.GreenControl")]
    public partial class GreenControl : UserControl, IControl
    {
        public GreenControl()
        {
            InitializeComponent();
        }
    }
}
Colourised in 8ms

The UserControl Class (ControlsLibrary2.dll ):

YellowControl.xaml:

 <UserControl x:Class="ControlLibrary2.YellowControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid Background="Yellow">           
    </Grid>
</UserControl>
Colourised in 6ms

YellowControl.xaml.cs:

namespace ControlLibrary2
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    [Export(typeof(IControl))]
    [ExportMetadata("Name", "ControlsLibrary2.YellowControl")]
    public partial class YellowControl : UserControl, IControl
    {
        public YellowControl()
        {
            InitializeComponent();
        }
    }
}
Colourised in 14ms

You can also download the code here:

1. How to use MEF fully lazy also for the Dll loading process?

Q: What can we do if we want MEF to be fully lazy also for the Dll loading process?

The problem is: MEF loads All the Dlls at the "path" folder!

At our example when loading Yellow Control the MEF mechanism loads 3 objects:

A: Solution 1: Use sub-directories:

Let’s Compile the ControlsLibrary1.dll & ControlsLibrary2.dll to Sub-Directories of the "Controls" Directory:

And Change the MainWindow code to be:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using ControlInterface;

namespace MEF_Example
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// Written by Shai Vashdi - Shai.Vashdi.Net@gmail.com - All rights reserved.
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        MEF.MEFLoader m_Loader = new MEF.MEFLoader();

        string GetPathByName(string name)
        {
            var res = AppDomain.CurrentDomain.BaseDirectory + @"Controls\";

            var familyname = name.Split(new[] { '.' })[0];

            return res + familyname;
        }

        UserControl GetControlByName(string name)
        {
            UserControl res = null;

            res =  m_Loader.LoadByTag<IControl>(GetPathByName(name), name).FirstOrDefault() as UserControl;

            return res;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var btn = (e.OriginalSource as Button);

            if (btn == null && btn.Tag != null)
                return;
            int number = 0;
            Int32.TryParse(btn.Tag as string,out number);

            switch (number)
            {
                case 1:
                    //Present Blue Control (dll: ControlsLibrary1.dll):
                    this.Worksapce.Content = GetControlByName("ControlsLibrary1.BlueControl");
                    break;
                case 2:
                    //Present Green Control (dll: ControlsLibrary1.dll):
                    this.Worksapce.Content = GetControlByName("ControlsLibrary1.GreenControl");
                    break;
                case 3:
                    //Present Yellow Control (diffrent dll: ControlsLibrary2.dll):
                    this.Worksapce.Content = GetControlByName("ControlsLibrary2.YellowControl");
                    break;
            }
        }
    }
}
Colourised in 55ms

Now when loading the Yellow control, only 1 object:

You can alse download the code here:

A: Solution 2: Use more Export interfaces:

Add to each dll his own inherited interface:

namespace ControlInterface
{
    public interface IControl
    {    
    }

    public interface IControlLibrary1 : IControl
    {
    }

    public interface IControlLibrary2 : IControl
    {
    }
}
Colourised in 4ms

Change the Export for UserControl Classes:

BlueControl.xaml.cs (ControlsLibrary1.dll ):

namespace ControlsLibrary1
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    [Export(typeof(IControlLibrary1))]
    [ExportMetadata("Name", "ControlsLibrary1.BlueControl")]
    public partial class BlueControl : UserControl, IControlLibrary1
    {
        public BlueControl()
        {
            InitializeComponent();
        }
    }
}
Colourised in 8ms

GreenControl.xaml.cs (ControlsLibrary1.dll ):

namespace ControlsLibrary1
{
    /// <summary>
    /// Interaction logic for GreenControl.xaml
    /// </summary>
    [Export(typeof(IControlLibrary1))]
    [ExportMetadata("Name", "ControlsLibrary1.GreenControl")]
    public partial class GreenControl : UserControl, IControlLibrary1
    {
        public GreenControl()
        {
            InitializeComponent();
        }
    }
}
Colourised in 9ms

YellowControl.xaml.cs (ControlsLibrary2.dll ):

namespace ControlLibrary2
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    [Export(typeof(IControlLibrary2))]
    [ExportMetadata("Name", "ControlsLibrary2.YellowControl")]
    public partial class YellowControl : UserControl, IControlLibrary2
    {
        public YellowControl()
        {
            InitializeComponent();
        }
    }
}
Colourised in 8ms

Load by Interface (MainWindow.xaml.cs):

namespace MEF_Example
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// Written by Shai Vashdi - Shai.Vashdi.Net@gmail.com - All rights reserved.
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        MEF.MEFLoader m_Loader = new MEF.MEFLoader();

        string GetPathByName(string name)
        {
            var res = AppDomain.CurrentDomain.BaseDirectory + @"Controls\";

            return res;
        }

        UserControl GetControlByName<T>(string name)
        {
            UserControl res = null;

            res =  m_Loader.LoadByTag<T>(GetPathByName(name), name).FirstOrDefault() as UserControl;

            return res;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var btn = (e.OriginalSource as Button);

            if (btn == null && btn.Tag != null)
                return;
            int number = 0;
            Int32.TryParse(btn.Tag as string,out number);

            switch (number)
            {
                case 1:
                    //Present Blue Control (dll: ControlsLibrary1.dll):
                    this.Worksapce.Content = GetControlByName<IControlLibrary1>("ControlsLibrary1.BlueControl");
                    break;
                case 2:
                    //Present Green Control (dll: ControlsLibrary1.dll):
                    this.Worksapce.Content = GetControlByName<IControlLibrary1>("ControlsLibrary1.GreenControl");
                    break;
                case 3:
                    //Present Yellow Control (diffrent dll: ControlsLibrary2.dll):
                    this.Worksapce.Content = GetControlByName<IControlLibrary2>("ControlsLibrary2.YellowControl");
                    break;
            }
        }
    }
}
Colourised in 43ms

Now when loading the Yellow control, only 1 object:

You can also download the code here:

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here