次の方法で共有


Simple Example of Managed Extensibility Framework (MEF) in Silverlight

As you may have heard, we recently shipped MEF support of Silverlight in our CodePlex drop..  I wanted to give you a very simple introduction to MEF and how to use it in Silverlight.   This example will show how to use lose coupling, dependency injection and delay loading of components.   This is also an update to my Simple MEF example from a few months ago as just about everything here applies to WPF, WinForms and ASP.NET apps as well. 

The demo requires (all 100% free):

  1. VS2008 SP1
  2. Silverlight 3 RTM
  3. MEF’s July CodePlex drop

Also, download the full demo files

Let’s start with the very cool Silverlight Navigation Application

image

Setting up The Problem

This is a very simple example just to show how the technology works.  Let’s say you want to put some string a TextBlock… As a bigger example, think of pages in an application or components in a portal, etc, etc.

Replace the contents of the ContentStackPanel in Home.xaml with a ListBox and set up some binding.

 <StackPanel x:Name="ContentStackPanel">
  
     <TextBlock Text="{Binding Mode=OneWay}"></TextBlock>
  
 </StackPanel>

Then in codebehind we set up the datacontext:

 public Home()
 {
     InitializeComponent();
     LayoutRoot.DataContext = "Hello World!";
 }

Of course, we get exactly what we’d expect…. 

Now there are some problems with this.. For example, the value of the string is tied up in the constructor, i might want to have that factored out so that it can be accessed separately. 

    1: public partial class Home : Page
    2: {
    3:     public Home()
    4:     {
    5:         InitializeComponent();
    6:  
    7:         var message = new SimpleHello();
    8:         LayoutRoot.DataContext = message;
    9:     }
   10: }
   11:  
   12: [Export("Message")]
   13: public class SimpleHello
   14: {
   15:     public override string ToString()
   16:     {
   17:         return "Hello World!";
   18:     }
   19: }

This will certainly do the trick at some level, but there is still something odd about lines 7 and 8… I am creating an instance of exactly this type which locks the functionality down to one instance.  That means the type has to be in my codebase.  What if I’d like to enable some separation?  maybe get that Message from a variety of sources based on config, the user preference, the role of the user, etc.

Enter MEF

Let’s see how MEF can help..  First we need to add a reference to the MEF assembly (System.ComponentModel.Composition.dll) that is found in the MEF_Preview_6\bin\SL3 folder of the latest bits.  I copied it into the bin directly of the project (which is hidden by default) and added a reference to it from there. 

As Jason Olson says: MEF is as easy as 1, 2, 3…Export It, Import It, and Compose It…

So the first step is Export it..  so let’s export the SimpleHello type… this is easy enough to do by simply putting an Export attribute on the type and specifying the contract name.

    1: [Export("Message")]
    2: public class SimpleHello
    3: {

Next step is to Import it, so let’s import  some type to databind to.

 [Import("Message")]
 public object Message; 
 public Home()
 {
     InitializeComponent();
  
     LayoutRoot.DataContext = Message;
 }

That looks very clean… I simply say what I have (an export) and what i need (an import).   But who does the wiring up?  Well, we have one more step… Compose It:

Add the following lines to the constructor…

    1: var catalog = new PackageCatalog();
    2: catalog.AddPackage(Package.Current);
    3: var container = new CompositionContainer(catalog);
    4: container.ComposeParts(this);

Line 1 shows off our new PackageCatalog that just new for this Silverlight release.  Catalogs generally tell MEF where to look to find stuff to put in the container.  Line 2 adds the current XAP to the catalog.. later we will show how to add XAPs that are asynchronously downloaded via a different catalog. 

Line 3 creates  a Composition container-- this is effectively the container that all the different parts will be wired up together.

Line 4 composes… that is wires up all the imports and exports. After this line, all imports are fully satisfied or an exception is raised.

Running the app has it work exactly the same way, but now loosely coupled.

image

That is great, but using strings such as “Message” to identify contracts is fraught with problems.  It turns out the CLR already has a pretty well developed way to deal with contracts and that is CLR Types.  So it is recommended in most cases that you use CLR Types to indicate the contracts.  To do this, let’s define an IMessage interface in a separate assembly so that it can be shared by different assemblies contributing components. 

Add a new project, and call it ContractsClassLibrary

image

Add a single type.. IMessage… right now it has no members, it is effectively a marker interface.  We could of course define some methods if they are needed.

 namespace ContractsClassLibrary
 {
     [InheritedExport]
     public interface IMessage
     {
  
     }
 }

Notice we put the new InheritedExport attribute on this type… this means that any type that implements this interface will automatically be exported as type IMessage.  This keeps the “MEF goo” out of your public object model.

Now, add a reference to ContractsClassLibrary from the MyMEFApp project and let’s look at how that SimpleHello type changes..

 public class SimpleHello : IMessage
 {
     public override string ToString()
     {
         return "Hello World";
     }
 }

Notice, no MEF goo on it… 

Fun and the app and you get the exact same results… but this time our contract is a CLR type rather than a string. 

image

Now, let’s say I had more than one message to show..  No problem, i just add another instance to the container with the same export. 

 public class SimpleHola: IMessage
 {
     public override string ToString()
     {
         return "Hola";
     }
 }

Hit F5 and… Wham.. I get an error..

image

(Side note: if you get a less helpful message that says “navigation failed”, in MainPage.cs change the ErrorWindow to show the inner exception…)

 private void ContentFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
 {
     e.Handled = true;
     ChildWindow errorWin = new ErrorWindow(e.Exception.InnerException);
     errorWin.Show();
 }

As these things go, it is actually a pretty helpful error message:

 The composition remains unchanged. The changes were rejected because of the
following error(s): The composition produced a single composition error. 
The root cause is provided below. Review the CompositionException.Errors 
property for more detailed information.

1) More than one exports were found that match the constraint 
'((exportDefinition.ContractName = "ContractsClassLibrary.IMessage") 
&& (exportDefinition.Metadata.ContainsKey("ExportTypeIdentity") && 
"ContractsClassLibrary.IMessage".Equals(exportDefinition.Metadata.get_Item("ExportTypeIdentity"))))'.

Resulting in: Cannot set import 'MyMEFApp.Home.Messages (ContractName="ContractsClassLibrary.IMessage")' on part 'MyMEFApp.Home'.
Element: MyMEFApp.Home.Messages (ContractName="ContractsClassLibrary.IMessage") --> MyMEFApp.Home


   at System.ComponentModel.Composition.CompositionResult.ThrowOnErrors(AtomicComposition atomicComposition)
   at System.ComponentModel.Composition.Hosting.ComposablePartExportProvider.Compose(CompositionBatch batch)
   at System.ComponentModel.Composition.Hosting.CompositionContainer.Compose(CompositionBatch batch)
   at System.ComponentModel.Composition.AttributedModelServices.ComposeParts(CompositionContainer container, Object[] attributedParts)
   at MyMEFApp.Home..ctor()

Basically it is saying that we were only expecting one message, and the container has more than one.. 

The easy solution here is to accommodate more than one Message..

 

 [ImportMany]
public ObservableCollection<IMessage> Messages { get; set; }

and let’s change the UI control to a ListBox…

 <ListBox ItemsSource="{Binding Mode=OneWay}"></ListBox>

Run it and we get both messages..

image

Another thing that is really cool about MEF is that we do full composition, that is even the parts in the container can have dependencies that are satisfied and runtime.   For example, let’s take out SimpleHello class and say we wanted to externalize providing the actual text… we could do that easily enough.

    1: public class SimpleHello : IMessage
    2: {   
    3:     [Import("Text")]
    4:     public string Text { get; set; }
    5:     public override string ToString()
    6:     {
    7:         return Text;
    8:     }
    9: }
   10:  
   11: public class TextProvider
   12: {
   13:     [Export("Text")]
   14:     public string Text { get { return "Hello World!"; } }
   15: }

We simply export the text from a provider class and import it into our SimpleHello class. (I am using the string contract name here just for simplicity, you could of course define an IText interface and use it exactly as we showed above.)   Notice the by product of this pattern is that each class gets very focused and specialized in what it deals with.  SimpleHello now is just about overriding ToString() and doesn’t care a thing about where it gets the message from.   That means we could easily add another way to offer text for binding… How about as a button?

    1: public class ButtoHello: Button, IMessage
    2: {
    3:     [Import("Text")]
    4:     public string Text {
    5:         get { 
    6:             return this.Content.ToString();
    7:         }
    8:         set
    9:         {
   10:             this.Content = value;
   11:         }
   12:     }
   13: }

This is time, we use the same text, but rather then ToString(), we use it to set the content property of the button.  This is a great example of a single instance exporting and importing dependencies. 

image

OK – that is cool… but all this is in the same project.. what if we wanted a bit more separation?   Well, the first step we could do is put some parts into a separate assembly, but deployed with the same XAP. 

image

We will need to reference our ContractsClassLibrary project.

Then just add a couple of classes that export “Message”

 public class Class1 : CheckBox, IMessage
 {
     public Class1()
     {
         this.Content = "From Assembly: ma kore?";
     }
 }
  
 public class Class2: IMessage
 {
     [Import("Text")]
     public string Text { get; set; }
  
     public override string ToString()
     {
         return "From Assembly:" + Text;
     }
 }

Notice, we can again, import the text optionally if we’d like.  This shares the exact same instance with any other type that imports it. 

Then we add a reference to this library from the main Silverlight app… this puts the library in the XAP

image

Now, run it and we get some more items.. 

image

Now the final step is to load the parts, asynchronously from the server.. This allows you to have components that are downloaded ONLY when they are needed.  It also enables much faster startup time where the core of the app can start up and features can light up as they are downloaded.  This is a much better experience than a long “loading…” screen.   

To do this, we need to create a library that is packaged as a XAP.. the best way to do that is to create a new Silverlight Application, and just delete the MainPage.xaml… 

image

Then we want to host this in the same web site.. .Notice you could host it in a different website, but you would then need to use the Silverlight networking stack’s mechanism to get at non-originating servers. 

image

You can go ahead and delete MainPage.Xaml and App.Xaml so we don’t get confused and add a Class1.cs file.

image

We will also need to add a reference to our ContractsClassLibrary project

Then just define a class an Export it.. 

 public class Class1: IMessage 
 {
     public override string ToString()
     {            
         return "From Dynamically Loaded: ciào";
     }
 }

You could optionally import the same Text property if you’d like as well.. 

 public class Class2 : ComboBox, IMessage 
 {
     [Import("Text")]
     public string Text
     {
         set
         {
             this.ItemsSource = value.ToCharArray();
         }
     }
 }

Now we need to go back into the main app and tell it where to dynamically load this from.

    1: public Home()
    2: {
    3:     InitializeComponent();
    4:  
    5:     var catalog = new PackageCatalog();
    6:     catalog.AddPackage(Package.Current);
    7:  
    8:     Package.DownloadPackageAsync(
    9:         new Uri("DynamicallyLoadClassLibrary.xap", UriKind.Relative),
   10:        (sender, pkg) => catalog.AddPackage(pkg) 
   11:     );
   12:  
   13:  
   14:     var container = new CompositionContainer(catalog);
   15:     container.ComposeParts(this);
   16:  
   17:  
   18:     LayoutRoot.DataContext = Messages;
   19: }

As you can see, we inserted lines 8-11 to deal with this new dynamically loaded XAP.  Here we are simply doing delay loading, but you could wait and load on some user action (clicking a button for example) or you could load based on who is logged in, what functionality they prefer, what role they are in, etc. 

In line 9, we create a URI pointing to our XAP.. in this case from the same server, but it could be from any server assuming the right policy files are in place. 
In line 10, we are handling the callback.. remember this is an async call.  When the call completes, we simply add any parts discovered to the catalog.

Now, the async nature of this is interesting, because effectively we are changing a programs dependencies mid-flight.  Not always a good idea, so it is something you need to opt into.   

 [ImportMany("Message",AllowRecomposition=true)]
 public ObservableCollection<object> Messages { get; set; }

And now we run and it looks great!

image

So, what have we seen?  You can use MEF to improve the quality and structure of your code so that classes have a single set of responsibilities.  MEF also makes it easy to optionally download components on demand. 

Check out the completed example

We’d love to hear what you think!

Comments

  • Anonymous
    July 20, 2009
    Brad, source code is not available from [http://brad_abrams.members.winisp.net/Projects/Silverlight3RTM/MyMEFApp.zip]

  • Anonymous
    July 21, 2009
    Can you talk about difference between MEF and Prism for Silverlight ? There is a lot of differents kinds of frameworks for Silverlight now and it is difficult to know the differences and when to use one or another.... Thank you Michel

  • Anonymous
    July 21, 2009
    Hi, I think I need to delve into MEF a little bit more as I cannot understand the concept you are telling. You can be little bit clear . Thanks, Thani

  • Anonymous
    July 21, 2009
    Congratulations. Very nice introduction about how to use MEF with Silverlight!

  • Anonymous
    July 21, 2009
    The comment has been removed

  • Anonymous
    July 21, 2009
    Thank you Glenn for the precisions. Last question, does MEF is supported by Microsoft ? Michel

  • Anonymous
    July 21, 2009
    Can you use mef in a commercial application or will that come later on??  

  • Anonymous
    July 21, 2009
    Michel, Yes, MEF ships as part of the .NET Framework and is fully supported. The releases on CodePlex are previews of what is to come in the framework. Bill, Yes you can, MEF ships on Codeplex under an MS-PL license thus you can use it without a "go-live" license.

  • Anonymous
    July 21, 2009
    Thank you for the answer Glenn  :))

  • Anonymous
    July 21, 2009
    Thank you for your reply. I would love to use this a asp.net mvc wcf restful web application. This is what I have been searching for in doing  very simple basic paragraph development.....        

  • Anonymous
    July 23, 2009
    Seem great i will check it soon

  • Anonymous
    July 30, 2009
    Nice post Brad! A simple but handy tip mentioned in the video here (http://development-guides.silverbaylabs.org/Video/Silverlight-MEF): you can decrease the size of DynamicallyLoadedClassLibrary.xap by setting 'CopyLocal' for the System.ComponentModel.Composition reference to 'false'. >> And now we run and it looks great! I like your code more than your UI design :P

  • Anonymous
    September 08, 2009
    Thank you Brad for you helpful posts! I load xamls from different modules. When I click some button on one page I need to send additional information from this page to another and react to this event on the second page. How can I do it with MEF?