Szymon Kobalczyk's Blog

A Developer's Notebook

  Home  |   Contact  |   Syndication    |   Login
  106 Posts | 6 Stories | 572 Comments | 365 Trackbacks

News

View Szymon Kobalczyk's profile on LinkedIn

Twitter












Tag Cloud


Article Categories

Archives

Post Categories

Blogs I Read

Tools I Use

Last Friday Managed Extensibility Framework Preview 2 (MEF) was published on CodePlex. MEF is a new library in .NET that will simplify adding extension points to your applications. It enables discovery, loading and composition of the extensible components. You can now download the source code, samples and find more information on the project site: http://www.codeplex.com/MEF

I was playing with the bits over the weekend and chatted with Glenn Block (Program Manager on the .NET FX team that does MEF), who explained to me what MEF can do for us. I thought it would be beneficial to share this information, so I created a sample to demonstrate how MEF can be hosted in your application. Please note that this covers only a single scenario where I found MEF might be useful, but MEF probably can do much more for you.

Before we go any further you might want to download the sample project, so you can browse the source code while I walk through it:

Download MefNavigationWindow project

Sample scenario

So here is my sample scenario: Let's say you are building a Windows Forms application where you want to add browser like navigation capabilities. This means that your window contains a placeholder where you can load other "pages" be specifying only the page name. Sounds familiar? Yes, WPF supports this out of the box with then NavigationWindow and Pages. So in short we would like to build a NavigationWindow for Windows Forms.

Let's assume that our pages would be simply UserControls. Actually it is quite easy to load user control dynamically - you simply instantiate the control's type and add it to parent's Controls collection. The real problem here is how to map page name (string) to it's type.

For example we could create a section in app.config file that lists all available views and map their names to the corresponding types. This could look similar to this:

<pageMappings>
  <page name="Page1" type="MefNavigationWindow.Page1" />
  <page name="Page2" type="MefNavigationWindow.Page2" />
  <page name="Page3" type="MefNavigationWindow.Page3" />
</pageMappings>

Then our NavigationWindow would load these mappings and construct the pages using reflection. But I can see two potential problems here.

  1. In case of a client applications the configuration file is easily available to users to mess with, and doing so it would likely break the application.
  2. Because the definition of class and mapping are kept in two separate places developers can easily forget to update the mappings when adding new pages to our solutions.

Therefore a better solution could be to create a custom attribute that is used to assign page name to a given user control:

[PageMetadata(PageName = "Page1")]
public partial class Page1 : UserControl
{
    //...
}

Then we can scan the current assembly for all types that have this attribute (using reflection), construct a dictionary of all pages, and when requested create instances of a specified page (with reflection again).

But guess what... MEF already does all this for us with much less code. Let me show you how.

Exports

First let's look at the PageMetadataAttribute:

[MetadataAttribute]
public class PageMetadataAttribute : Attribute
{
    public string PageName { get; set; }
}

Very simple. Only odd thing is that this attribute itself need to be tagged with MetadataAttribute so MEF knows to expose it as metadata in the parts catalog (and we will use it in just a moment). Now we can use it on any UserControl to assign the page name, but we need to use it together with the Export attribute. Type discovery in MEF is based on attributes and Export indicates that the attributed class should be exported as ComposablePart and specifies the contract it implements (it is Page in our case):

[Export("Page")]
[PageMetadata(PageName = "Page1")]
public partial class Page1 : UserControl
{
    // ...
}

Notice that in MEF contract is specified as string. There is additional overload on the Export attribute that consumes a Type, but internally it would be converted to a fully qualified name of that tape. On the side note, even when you use a typed contract the attributed class doesn't really need to implement it - the contract type is used only as a key in the container.

CompositionContainer and Catalogs

Now we can start implementing our NavigationWindow and first thing to do is to configure the CompositionContainer:

private void InitializeCompositionContainer()
{
    var catalog = new AttributedAssemblyPartCatalog(typeof (NavigationWindow).Assembly);
    var container = new CompositionContainer(catalog.CreateResolver());

    container.AddPart(this);
    container.Compose();

    _container = container;
}

Because we want MEF to discover all the pages in the current assembly we use the AttributedAssemblyPartCatalog that will search for all types that were attributed as exports. Catalogs are responsible for creating a Resolver that is then passed to the CompositionContainer so it can query for available ComposableParts (you can learn about the other two types of catalogs from the programming guide). Next we also register the NavigationWindow itself as a ComposablePart in the container. After doing so we need to call the Compose method to resolve dependencies (note that you don't need to call Compose to user parts exposed from catalogs). But for this to work properly we need to add Export attribute on NavigationWindow too:

[Export("NavigationWindow")]
public partial class NavigationWindow : UserControl

Imports

Ok, it's about time to talk about the dependencies, aka imports. For the NavigationWindow this will be the list of all available page types and we can get this by declaring following collection:

[Import("Page")]
private ExportCollection<UserControl, IPageMetadata> _pages;

The Import attribute indicates that container should resolve this dependency for us by assigning a collection of all exports with Page contract (this happens when you call container's Compose method). It will also expose the PageMetadataAttribute through IPageMetada interface:

public interface IPageMetadata
{
    string PageName { get; set; }
}

Notice that we didn't implement this interface earlier on the attribute itself. At runtime MEF will be able to create dynamic proxy to it with a magic thing called duck typing. Isn't that cool!

The _pages field is private and normally it wouldn't be resolved, but we can ask MEF to handle it by adding the assembly level AllowNonPublicComposition attribute (add it to AssemblyInfo).

Now we are ready to implement the GoTo method that will do the navigation:

public void GoTo(string pageName)
{
    var newPage = _pages.SingleOrDefault(e => e.MetadataView.PageName == pageName);

    Controls.Clear();

    if (newPage != null)
    {
        var control = newPage.GetExportedObject();
        control.Dock = DockStyle.Fill;
        Controls.Add(control);
    }
}

In the first line it will search the _pages collection for one with specified page name (MetadataView property implements our IPageMetada interface). Later when the page was found we request the instance of UserControl with call to GetExportedObject(). I told you this would be easy!

Now we can put the NavigationWindow on any Form and call navigationWindow1.GoTo("Page1") to get this:

image 

I assume that we would like to navigate from one page to another by clicking the link. Therefore the page needs to know the NavigationWindow it is hosted in – in other words it has a dependency on the NavigationWindow.

[Import("NavigationWindow")]
public NavigationWindow NavigationWindow { get; set; }

private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
    NavigationWindow.GoTo("Page2");
}

This is why earlier we registered the NavigationWindow itself as a ComposablePart, so now we can import it from any other part. This would get us to the second window:

image

Object lifetime

To make this exercise more interesting here we have a TextBox and we want to pass its value to the third window. But first let's talk briefly about part's lifetime. By default all the exported parts are treated as singletons. For us this means that there would be only a single instance ever created of each page (as soon as you call Export.GetExportedObject method). Of course this is not always the desired scenario, and you can change this behavior using the CreationPolicy property of the CompositionOptions attribute. Here is Page1 revisited:

[CompositionOptions(CreationPolicy = CreationPolicy.Factory)]
[Export("Page")]
[PageMetadata(PageName = "Page1")]
public partial class Page1 : UserControl

The CreationPolicy enumeration has only two values: Singleton (default), and Factory.

Looking back to our scenario we want to make the second page a singleton so it can be accessed from the third page. To access it would also need to get a reference to the Container – and again we do this by adding another dependency in Page3:

[Import("Container")]
public CompositionContainer Container { get; set; }

Now, this dependency won't be available just yet. As we've seen before the container was created by the NavigationWindow. We could register it manually as we did with the NavigationWindow, but I would like to show you one more capability that MEF has – you can declare Exports not only on types but also on the class members:

[Export("Container")]
private CompositionContainer _container;

So when we add this field to NavigationWindow we now not only export the NavigationWindow itself but also the value in the it's _container field. I can imagine that this capability would be handy if you need to export one of the built-in system types or type from a third-party library when you can't add the Export attribute directly on the given type.

Finally here is the code from Page3 to get the reference to previous page and read the value entered in the TextBox:

private void PageLoaded(object sender, EventArgs e)
{
    var page = Container.GetExports<IAskNamePage>(p => p.ContractName == "Page" && string.Equals(p.Metadata["PageName"], "Page2")).Single();
    nameLabel.Text = "Helo " + page.GetExportedObject().FullName;
}

This time we used one of container's GetExports methods to query for a page (ComposablePart with contract "Page") and with page name of "Page2" (through Metadata dictionary). The query capability on metadata is one of the big things in MEF and it allows you to get detailed information about available parts even before you instantiate them. I've also added IAskNamePage interface so we can get the text entered on Page2:

public interface IAskNamePage
{
    string FullName { get;  }
}

It simply returns the text from TextBox:

public string FullName
{
    get { return textBox1.Text; }
}

Here is the last page:

image

Summary

I hope that this example gives you some idea where MEF fits and will help you get started. Let me know if you have any questions about this. For more advanced scenario check out the three samples that shipped with the current drop on CodePlex:

  • MEFlook - Outlook like client
  • MEFTris - Tetris like game with shapes as plug-ins
  • Extensible File Explorer - File explorer with extensible views, favorite file viewers and shell services
  • posted on Monday, September 8, 2008 1:38 PM

    Feedback

    # re: Getting started with Managed Extensibility Framework 9/10/2008 7:16 AM Krzysztof Cwalina
    Really nice! Thanks for writing it!

    # re: Getting started with Managed Extensibility Framework 9/26/2008 7:34 AM Glenn Block
    Nice job Szymon. So when do we get to see the WPF version again? :-)

    # re: Getting started with Managed Extensibility Framework 12/3/2008 11:35 AM Ditlef
    I upgraded the MEF to version 3 just out.
    The intended logic in your demo by recreating the Page3 each time you visit the page stopped working.
    Same instance is returned each time.
    It seems to be quite cumbersome to get this behaviour in version 3.
    Any pointers?
    Cheers
    Ditlef


    # re: Getting started with Managed Extensibility Framework 12/3/2008 11:38 AM Szymon
    Hi Ditlef, thanks for pointing this out. I would try to update my sample for latest MEF drop in next few days.

    -Szymon

    # re: Getting started with Managed Extensibility Framework 10/23/2010 5:45 PM payday loans online
    This post is really great! I have been searching for some information about the Managed extensibility framework and accidentally I have noticed this headline. As I see, this site is full of more such great posts like this one so I will definitely bookmark it. Thanks a lot one more time.


    # re: Getting started with Managed Extensibility Framework 11/21/2010 8:01 AM ares free download
    Great post

    # re: Getting started with Managed Extensibility Framework 11/29/2010 2:13 PM free online games rpg
    Come and play free online games rpg and others role playing game online


    # re: Getting started with Managed Extensibility Framework 11/29/2010 2:14 PM health care
    Great effort

    # re: Getting started with Managed Extensibility Framework 7/7/2011 8:48 AM joomla template
    Nic effort

    Post A Comment
    Title:
    Name:
    Email:
    Comment:
    Verification: