Nearly a year ago I was playing with Unity container in the scope of pure Xaml and Markup extensions. There was a 4-part series of articles on my old blog:
Injecting Xaml with Unity Application Block using Markup Extensions. Part 4
This time I’ve decided to continue my extreme development and to try injecting UI controls into WPF layout directly from MEF
Note that this is an experimental stuff, I know about Prism/CompositeWPF and many other things. I was just intrested whether it is possible do it the way I did ;)
1. Preparing Layout
I wanted to have something like the following:
<Window
x:Class="XamlifiedMEF.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:XamlifiedMEF"
Title="Xamlified MEF" Height="600" Width="800">
<StackPanel>
<local:MefPart Contract="TextBoxPart"/>
<ItemsControl ItemsSource="{local:MefPart Contract=ButtonPart}"/>
</StackPanel>
</Window>
where the StackPanel element imports one TextBox (via the “TextBoxPart” contract) and additionally nests a single ItemsControl having an ItemsSource as a collection of exported Button elements (via the “ButtonPart” contract).
2. MEF Locator
As a common Xaml MarkupExtension cannot be imported or built up via some sort of ObjectBuilder (in order to get dependencies injected) you might need some static entry point (Service Locator). I recommend reading the article “CommonServiceLocator for MEF, a service is a service” by Glenn Block or referring to codeplex version of CommonServiceLocator.
In my case I simply decided to use the static class in order to get access to CompositionContainer:
namespace XamlifiedMEF
{
internal static class Mef
{
private static readonly CompositionContainer _container;
public static CompositionContainer Container
{
get { return _container; }
}
static Mef()
{
_container = new CompositionContainer(
new AssemblyCatalog(
Assembly.GetExecutingAssembly()));
}
}
}
Of course in “real” implementations this service might require proper implementaiton and configuration but I left it simple for clarification purposes.
3. Parts
Next thing you will need is defining a couple of parts to be injected/imported into the layout. I’ve created a dummy textbox and three buttons:
namespace XamlifiedMEF
{
[Export("TextBoxPart")]
public class TextBoxPart : TextBox
{
public TextBoxPart()
{
this.Text = "Hi, I'm imported text part";
}
}
[Export("ButtonPart")]
public class Button1 : Button
{
public Button1()
{
this.Content = "I'm a button part 1";
}
}
[Export("ButtonPart")]
public class Button2 : Button
{
public Button2()
{
this.Content = "I'm a button part 2";
}
}
[Export("ButtonPart")]
public class Button3 : Button
{
public Button3()
{
this.Content = "I'm a button part 3";
}
}
}
The samples above are fairly simple and guess need no comments
4. Markup Extension
And finally you will need a simple markup extension that will get one or several parts from the MEF container. Here’s the smallest extension that could be implemented in this case:
namespace XamlifiedMEF
{
[MarkupExtensionReturnType(typeof(FrameworkElement))]
public class MefPartExtension : MarkupExtension
{
[ConstructorArgument("Contract")]
public string Contract { get; set; }
public MefPartExtension()
{
}
public MefPartExtension(string contract)
{
this.Contract = contract;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
ExportCollection<FrameworkElement> exports =
Mef.Container.GetExports<FrameworkElement>(Contract);
if (exports.Count == 0) return null;
if (exports.Count == 1) return exports[0].GetExportedObject();
var items = from Export<FrameworkElement> item
in exports
select item.GetExportedObject();
return items;
}
}
}
Note: The extension is marked with FrameworkElement return type in case a single UI part is imported.
The core of the extension is “ProvideValue” method that should be always implemented. You will need to get the collection of exports by provided contract name. Next I determine whether ther resulting collection contains one element or several and return the values according to the result.
When you will launch the application you will see that all the declared elements were successfully imported into the layout.
Demo project can be downloaded here…
