Click here to Skip to main content
15,885,216 members
Articles / Desktop Programming / WPF

The Simplest Way to use MEF Fully Lazy DLL Loading

Rate me:
Please Sign up or sign in to vote.
4.87/5 (32 votes)
3 Mar 2014CPOL3 min read 79K   3.1K   104   12
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:

C#
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;
        }
    }
}
  • 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:

Image 1

The Implementation, MainWindow.xaml:

XML
<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>

Code Behined, MainWindow.xaml.cs:

C#
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;
            }
        }
    }
}

The UserControl Classes (ControlsLibrary1.dll ):

BlueControl.xaml:

XML
<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>

BlueControl.xaml.cs:

C#
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();
        }
    }
}

GreenControl.xaml:

XML
<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>

GreenControl.xaml.cs:

C#
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();
        }
    }
}

The UserControl Class (ControlsLibrary2.dll ):

YellowControl.xaml:

XML
 <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>

YellowControl.xaml.cs:

C#
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();
        }
    }
}

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:

Image 2

A: Solution 1: Use sub-directories:

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

Image 3

And Change the MainWindow code to be:

C#
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;
            }
        }
    }
}

Now when loading the Yellow control, only 1 object:

Image 4

You can alse download the code here:

A: Solution 2: Use more Export interfaces:

Add to each dll his own inherited interface:

C#
namespace ControlInterface
{
    public interface IControl
    {    
    }

    public interface IControlLibrary1 : IControl
    {
    }

    public interface IControlLibrary2 : IControl
    {
    }
}

Change the Export for UserControl Classes:

BlueControl.xaml.cs (ControlsLibrary1.dll ):

C#
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();
        }
    }
}

GreenControl.xaml.cs (ControlsLibrary1.dll ):

C#
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();
        }
    }
}

YellowControl.xaml.cs (ControlsLibrary2.dll ):

C#
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();
        }
    }
}

Load by Interface (MainWindow.xaml.cs):

C#
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;
            }
        }
    }
}

Now when loading the Yellow control, only 1 object:

Image 5

You can also download the code here:

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Chief Technology Officer GalilCS
Israel Israel
My name is Shai Vashdi and I’m the CTO & Co-Founder of GalilCS.
GalilCS provides industrialized, low-cost IT services (ILCS) to corporations around the globe. We specialize in code modernization projects (VB6 to C# WinForms/HTML5, for example) and code refactoring projects.
Most of my work revolves around the management of these projects and the creation of new tools for refactoring code. As a bonus, I also get to lecture about Microsoft programming languages.
For more information, visit GalilCS.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Member 1544345425-Nov-21 5:25
Member 1544345425-Nov-21 5:25 
QuestionGood MEF example Pin
Orence22-Mar-14 22:04
Orence22-Mar-14 22:04 
QuestionAbout MEF and WinForm Pin
Orence13-Mar-14 23:49
Orence13-Mar-14 23:49 
AnswerRe: About MEF and WinForm Pin
Shai Vashdi22-Mar-14 7:19
Shai Vashdi22-Mar-14 7:19 
GeneralRe: About MEF and WinForm Pin
Orence22-Mar-14 21:58
Orence22-Mar-14 21:58 
GeneralRe: About MEF and WinForm Pin
Shai Vashdi22-Mar-14 22:52
Shai Vashdi22-Mar-14 22:52 
GeneralMy vote of 2 Pin
Dave Kreskowiak3-Mar-14 3:16
mveDave Kreskowiak3-Mar-14 3:16 
GeneralRe: My vote of 2 Pin
Shai Vashdi3-Mar-14 22:33
Shai Vashdi3-Mar-14 22:33 
SuggestionTypo Pin
bling27-Feb-14 9:59
bling27-Feb-14 9:59 
GeneralRe: Typo Pin
Shai Vashdi2-Mar-14 21:26
Shai Vashdi2-Mar-14 21:26 
QuestionMore simple? Pin
George Swan25-Nov-12 22:28
mveGeorge Swan25-Nov-12 22:28 
AnswerRe: More simple? Pin
Shai Vashdi26-Nov-12 1:38
Shai Vashdi26-Nov-12 1:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.