Building Hello MEF – Part II – Metadata and why being Lazy is a good thing.

In Part I of the series we built the basics of our dashboard end ended with our app displaying a single widget. In this post we’ll show two widgets,  sensing a pattern here?  :-) We will show two, but we’ll put them in different places on our dashboard. We’ll explore two ways of doing this including a very powerful feature in MEF called metadata. Then we’ll see how we can use custom exports to provide our own “language’ of sorts for our extenders. 

If you are just jumping into this post, you can grab the code for where we left off here.

Adding a second widget

OK, here we go. Now that we have our dashboard infrastructure in place, should be easy to add a second widget right? Right. Let’s do it, this time we’ll add a text box.

First we’ll go create a new user control called Widget2

image

Next we’ll go into the Widget2.xaml and add a button with the content “Hello MEF”.

 <UserControl x:Class="HelloMEF.Widget2"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
    
    <Grid x:Name="LayoutRoot" Height="Auto">
        <TextBox Text="Hello MEF!" TextAlignment="Center" Height="100" Width="300"
    </Grid>
</UserControl>

Then we’ll jump into Widget2.xaml.cs and add our export.

 using System;
using System.ComponentModel.Composition;

using System.Windows.Controls;

using System.Windows;
namespace HelloMEF
{
    [Export(typeof(UserControl))]
    public partial class Widget1 : UserControl
    {
        public Widget1()
        {
            InitializeComponent();
        }
    }
}

All we did was create a new control and drop an export (the only MEF port). Let’s run it.

image

Presto! The second widget shows up. No app.config editing, no explicit registration code. Just add it to the project and we’re done. Not bad!

Adding a second widget where we want it, and why being Lazy is a good thing.

OK well that’s pretty nice. But if you read my first post, we want to have widgets showing up in different places as we have 2 panels for widgets in our screen. One which is blue, and one which is yellow but currently invisible. So what can we do? Well one option is to create different interfaces, ITopWidget, IBottomWidget. Then we can check if the class implements those interfaces once we get it and just put it in the right place.

If we did, our code would in our MainPage would look something like this

 public MainPage()
{
    InitializeComponent();
    PartInitializer.SatisfyImports(this);
    foreach (var widget in Widgets)
    {
        if (widget is ITopWidget)
            TopWidgets.Items.Add(widget);
        else if (widget is IBottomWidget)
            BottomWidgets.Items.Add(widget);
    }
}

That works, however it forces us to ship new interfaces every time I we want to enhance the UI and add a left pane, we need to ship ILeftWidget. A different problem is that we are creating all widgets even if they don’t implement any of our interfaces. But what else can we do?

Lazy

The answer is to become Lazy. Yes folks, this is the one time when Lazy is a good thing. If you are not familar, Lazy<T> is new type in the SL4 and FX4 BCL that allows you to delay the creation of an instance. MEF fully supports Lazy, thus you can import single values and collections in a delayed manner. For example we can change our ImportMany to use Lazy, by pasting the following code in MainPage.

 using System;
using System.Windows.Controls;

using System.ComponentModel.Composition;
namespace HelloMEF
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            PartInitializer.SatisfyImports(this);
            foreach (var widget in Widgets)
            {
                TopWidgets.Items.Add(widget.Value);
            }
        }
        [ImportMany]
        public Lazy<UserControl>[] Widgets { get; set; }
    }
}

Two things you should notice above, first our Widgets import is now an array of Lazy<UserControl>, and second in the loop we are adding widget.Value rather than the widget value directly. What is going on here is instead of importing UserControl instances we are importing lazy references to our widgets. The first time Value is accessed, the UserControl is created, thus we access .Value to add the widgest to the TopWidgets ItemsControl.

Metadata

Well that’s nice, because now we’ve delayed creating the controls until we actually need them. The downside is we have no way to determine which one’s we should put where which was our goal. And THAT is where metadata comes to the rescue. Not only does MEF support lazy instantiation, but it also supports a concept called export metadata. This means that a MEF export can give off additional information about itself that the importer can use for several reasons, whether it be determining whether or not it should be created, or in our case determining where it will show on the screen. The importer can access this information BEFORE the instance has actually been activate. This means if the particular export does not match the requirements, then it does not have to be created.

Metadata is arbitrary in MEF, that means you can use it however you see fit according to your application needs. Using metadata has two parts, first there is the exporter who defines which metadata is available for importers to look at. Then there is the importer who is able to access the metadata at the time of import.

Defining the metadata for widgets

In our case we want our widgets to provide us a location. In order to do that, we’ll first create a new enum called WidgetLocation.

 namespace HelloMEF
{
    public enum WidgetLocation {Top, Bottom}
}

Then we’ll go add a piece of metadata called Location to our widgets. There are several ways to do this in MEF, the easiest of which to start with is simple adding an ExportMetadata attribute. First we’ll go to Widget1.xaml.cs and set the widget’s location to the top as you can see below. Notice, the key and value are arbitary to my specific needs.

     [ExportMetadata("Location", WidgetLocation.Top)]
    [Export(typeof(UserControl))]
    public partial class Widget1 : UserControl
    {
        public Widget1()
        {
            InitializeComponent();
        }
    }

Next we’ll go to Widget2 and tell it to set it’s location to the bottom.

     [ExportMetadata("Location", WidgetLocation.Bottom)]
    [Export(typeof(UserControl))]
    public partial class Widget2 : UserControl
    {
        public Widget2()
        {
            InitializeComponent();
        }
    }

Now we can move on to the importer

Giving the dashboard access to the widget metadata and why Lazy has a brother.

Now that we’ve defined the metadata we just need to use it. We talked about Lazy<T>, let’s talk about it’s brother Lazy<T,M>. In order to allow us to access the metadata, MEF introduces a special kind of Lazy, one that has attached metadata. M in this case is an interface (we call it a metadata view)  that contains only getter properties where the property name corresponds to the key of a piece of metadata. MEF will automatically general a proxy class that implements this interface and it will plug all the metadata in for you. This is very cool! You need to see it to believe it (and understand it).

So let’s just do it rather than talking about it.

First, we’ll create a new interface call IWidgetMetadata which contains our Location metadata.

 namespace HelloMEF
{
    public interface IWidgetMetadata
    {
        public WidgetLocation Location {get;}
    }
}

Next we’ll go into our MainPage.xaml.cs, and change our Widgets import to use the new metadata. We’ll also change the logic to place the widgets in the appropriate place.

 using System;
using System.Windows.Controls;

using System.ComponentModel.Composition;
namespace HelloMEF
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            PartInitializer.SatisfyImports(this);
            foreach (var widget in Widgets)
            {
                if (widget.Metadata.Location == WidgetLocation.Top)
                    TopWidgets.Items.Add(widget.Value);
                else if (widget.Metadata.Location == WidgetLocation.Bottom)
                    BottomWidgets.Items.Add(widget.Value);
            }
        }
        [ImportMany]
        public Lazy<UserControl,IWidgetMetadata>[] Widgets { get; set; }
    }
}

Above you can see that Widget’s item type is now Lazy<UserControl,IWidgetMetadata>. Also in the for loop we’re accessing widget.Metadata.Location in a compile safe manner, but we never created a concrete class that implements IWidgetMetadata, did we? We didn’t, but MEF did :-) The result is no magic strings on the importer side, we get intellisense, refactoring and compile time checking! Also notice we still need to access the .Value to get the widget to be created. This is actually an advantage though because if we get a widget with a different location, it won’t get created wastefully. Here it makes no big difference, but if we had 50 widgets that is a different story.

Now we’ll go run the app.

image

And Voila we see our widgets are now going in the right place, and we didn’t have to do 1000 back flips.

Magic strings are bad! And MEF has an answer.

Notice above I mentioned we get all this compile time safety and intellisense support for the importer. But what about the exporter? Our widgets are still specifying magic strings for the keys. Not only that, but imagine if I have multiple pieces of metadata, then it’s not just one string, it’s many strings for each part. Plus I lose a lot of discoverability because I don’t know intuitively when I create a widget, what metadata keys make sense. Even if we create constants for the keys, we don’t know the type.

Wouldn’t it be really nice if we could just do this:  [ExportWidget(Location=WidgetLocation.Top)] ? We wouldn’t need Export, and we wouldn’t need all those loose ExportMetadata attributes. It would be cleaner, and our code would be less noisy.

The good news is you can.

Custom exports

MEF let’s you create custom exports that include metadata. A custom export is just that, it’s your own export attribute with metadata, and it’s strongly typed.

Let’s create our new ExportWidgetAttribute.

 using System;
using System.Windows.Controls;

using System.ComponentModel.Composition;
namespace HelloMEF
{
    [MetadataAttribute]
    [AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
    public class ExportWidgetAttribute : ExportAttribute
    {
        public ExportWidgetAttribute()
            :base(typeof(UserControl))
        {
        }
        public WidgetLocation Location { get; set; }
    }
}

If we look above there are a few things to note:

  1. ExportWidgetAttribute inherits from ExportAttribute. This is what tells MEF “Hey I am an export attribute”
  2. The base constructor on Export is called passing in UserControl. This step is critical or MEF will default to the concrete implementation that the attribute was dropped on.
  3. The attribute has a [MetadataAttribute] on it, this tells MEF this is not only an export but it also provides metadata. Now that this is applied, MEF will look at each public property on the attribute that I added, and will assume it is metadata, with the name being the key. In essence it will add an [ExportMetadata(“Location”,…)] with whatever value is specified for the property.
  4. The attribute has an [AttributeUsage] attribute specifying the acceptable targets i.e. class in this case and that AllowMultiple = false. If we don’t do this, then MEF will assume multiple sets of metadata can exist and will return the metadata as an array.

Now we can go and change each of our widgets to use the new attribute.

First Widget1.xaml.cs

 using System.Windows.Controls;
namespace HelloMEF
{
    [ExportWidget(Location=WidgetLocation.Top)]
    public partial class Widget1 : UserControl
    {
        public Widget1()
        {
            InitializeComponent();
        }
    }

And then Widget2.xaml.cs

 using System.Windows.Controls;
namespace HelloMEF
{
    [ExportWidget(Location=WidgetLocation.Bottom)]
    public partial class Widget2 : UserControl
    {
        public Widget2()
        {
            InitializeComponent();
        }
    }
}

Our custom attributes our quite nice. No more specifying a contract, no more loose  metadata, and it’s nice and compact. Also notice our usings above as well, no more need to specify System.ComponentModel.Composition…..it’s our attribute. This means our customers can get going using MEF without even knowing they are using it. We just send them OUR attributes and OUR interfaces, and they are good to go.

Go and press the magic run button and this what you get.

image

Any questions? :-)

 

In Summary

In this post we learned how we can use metadata and Lazy to do some pretty nice things with MEF. We can annotate our exports with information that importers can use to determine whether they want to use us, and how they should us (i.e. put me in the top location). We also learned how can create our own custom expors to offer a nice experience.

What’s next?

In the next post we’ll explore how to partition our apps into multiple XAPs and use MEF to deliver them on-demand. We’ll also learn a few other aspects of MEF that may not be obvious. There’s much more to come after that, the series is shaping up!

Code is attached

https://cid-f8b2fd72406fb218.skydrive.live.com/self.aspx/blog/Hello%20MEF/HelloMEF%5E_Part%5E_II.zip