Introduction
This article provides an easy a generic approach of working
with MEF:
- The Simplest way to (generically)
use MEF.
- 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
{
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);
}
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);
}
}
public interface IMetadata
{
string Name { get; }
}
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)
{
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(directoryCatalog);
CompositionContainer container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
public ICollection<T> LoadByMEF(string path, string name)
{
var res = new List<T>();
DoImport(path);
foreach (Lazy<T, IMetadata> module in imports)
{
if (module.Metadata.Name == name || String.IsNullOrEmpty(name))
{
res.Add(module.Value); }
}
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
{
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:
this.Worksapce.Content = GetControlByName<IControl>("ControlsLibrary1.BlueControl");
break;
case 2:
this.Worksapce.Content = GetControlByName<IControl>("ControlsLibrary1.GreenControl");
break;
case 3:
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
{
[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
{
[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
{
[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
{
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:
this.Worksapce.Content = GetControlByName("ControlsLibrary1.BlueControl");
break;
case 2:
this.Worksapce.Content = GetControlByName("ControlsLibrary1.GreenControl");
break;
case 3:
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
{
[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
{
[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
{
[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
{
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:
this.Worksapce.Content = GetControlByName<IControlLibrary1>("ControlsLibrary1.BlueControl");
break;
case 2:
this.Worksapce.Content = GetControlByName<IControlLibrary1>("ControlsLibrary1.GreenControl");
break;
case 3:
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: