Plug-ins and composite applications in Silverlight – pt 2

In my previous post I wrote a demo application that used a custom implementation to load plug-ins from the server and place them in a shell. This implementation was very specific and limited in its functionality. It did however do what I needed for the simple task at hand. This post is all about how we can do the same thing using the Managed Extensibility Framework (MEF). MEF is of course a lot more flexible and powerful than my simple implementation, and then what I am about to use it for. But I will build the same application using MEF anyway, and show you how it works in comparison to the custom solution.

The first thing that we need to do, is to download the stuff you need. On Glen Block’s blog blog, he says that you need to download the latest bits of codeplex to get his code working. But to be honest, I just used the bits in the Silverlight 4 SDK Beta, and it seems to be working fine. I guess the SDK has been updated with new bits since his blog post. So go ahead and download the Silverlight 4 SDK Beta

Some of the MEF framework is in Silverlight 4, and more will probably be added before release. More about this on Glen’s blog. But for now, we can use the SDK to get what we need…

But before I start looking at the gnarly bits, I want to mention that I have taken a lot of information and also some code to be honest from Glen’s blog. Mostly from this series of blog posts. So if you recognize some of my code, then that might be it. So why have I borrowed/stolen from Glen? Well, Glen is the man when it comes to MEF. And his intro was really interesting. I especially like his way of abstracting away some code into a service interface and use MEF to inject it. Very smooth… So I stole it… I admit… But it was on his blog, so it isn’t really stealing… So thanks Glen for the help. BTW, I have actually met Glen a while back, and he seemed like a very nice guy that wouldn’t be to angry at me for borrowing some of his code…

Anyhow…let’s get going.

I start my implementation by modifying the ExtensionsService that I created in the previous post. I add a new operation called GetMEFExtensionLibraries(). It is identical to the GetCustomExtensionLibraries() method, except that it changes the folder where it looks for extensions.

[OperationContract]
public IEnumerable<string> GetMEFExtensionLibraries()
{
DirectoryInfo dir = new DirectoryInfo(HttpContext.Current.Server.MapPath("/ClientBin/MEFExtensions/"));
return dir.GetFiles().Select(fi => "/ClientBin/MEFExtensions/" + fi.Name);
}

I start by adding a Silverlight Class Library project to my solution. I call it MEF.Common. In this project, I will add the parts of the example that is common between the plug-ins and the shell, just as in the previous example. I start by adding a single class called ApplicationExtension. It inherits from UserControl, but has no other implementation. This will just be used to identify my plug-ins. Once again, just like in the previous post.

I could have used an interface as well, but I chose a class, as I need to get access to a UserControl in the end. So I had to either inherit from UserControl, or have an interface that exposed a UserControl object. Inheriting felt nicer…

public class ApplicationExtension : UserControl
{
}

 

Next, I replicate the plug-ins (clocks) from the previous post. I place them in a new Silverlight Application project, just like I did last time, and then remove the app.xaml and manpage.xaml files. I also add a post-build script that copies the Xap to the correct folder, so that the service will find it. From an implementation perspective, the only difference from previous post is a lack of the property called Location. The location of the plug-ins will be solved in a different way in MEF.

public partial class DigitalClock : ApplicationExtension
{
public DigitalClock()
{
InitializeComponent();
Loaded += new RoutedEventHandler(DigitalClock_Loaded);
}

void DigitalClock_Loaded(object sender, RoutedEventArgs e)
{
SetText();
DispatcherTimer tmr = new DispatcherTimer();
tmr.Interval = TimeSpan.FromSeconds(1);
tmr.Tick += (o, a) => { SetText(); };
tmr.Start();
}

private void SetText()
{
ClockText.Text = DateTime.Now.ToString("HH:mm:ss");
}
}

 

public partial class AnalogClock : ApplicationExtension
{
public AnalogClock()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(ControlLoaded);
}

void ControlLoaded(object sender, RoutedEventArgs e)
{
SecondsAngle.Angle = 360 * (DateTime.Now.Second / 60f);
SecondsAnimation.Begin();

MinutesAngle.Angle = 360 * (DateTime.Now.Minute / 60f);
MinutesAnimation.Begin();

HoursAngle.Angle = 360 * (DateTime.Now.Hour / 12f);
HoursAnimation.Begin();
}
}

 

I only show the C# code as the Xaml is identical to one used in the previous post.

Finally, I create the shell application. It is a standard Silverlight Application project. The Xaml for the MainPage.xaml is once again a replica of the one used in the previous post. It contains a Grid with two columns…

<UserControl x:Class="DarksideCookie.ExtensibilityDemo.MEF.Shell.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
</Grid>
</UserControl>

 

As I open the code-behind for the MainPage.xaml, I start out by creating almost the the same code as in the previous post. It is really getting annoying writing “…as in the previous post”. As you can see, a lot of the application is identical. But we will soon start looking at the differences.

In the constructor, I attach a handler to the GetCustomExtensionLibrariesCompleted event and go on by making a call to the ExtensionService’s GetCustomExtensionLibrariesAsync(). In the handler, I check for errors before going on…

public MainPage()
{
InitializeComponent();

ExtensionService.ExtensionsServiceClient svc = new ExtensionService.ExtensionsServiceClient();
svc.GetMEFExtensionLibrariesCompleted += ExtensionsReceived;
svc.GetMEFExtensionLibrariesAsync();
}

void ExtensionsReceived(object sender, ExtensionService.GetMEFExtensionLibrariesCompletedEventArgs e)
{
if (e.Error != null)
return;

...
}

Now it is time to start looking at what MEF can do for us. MEF works sort of like an IOC container. You basically mark a class as “exporting” a type, and when you want to use it, you mark a property of the correct type on your class with an “import” marker. These “markers“ are of course attributes.

Let’s start by looking at exporting our plug-ins. This is really easy. Just add a reference to the System.ComponentModel.Composition assembly that you can find in %Program Files%\Microsoft SDKs\Silverlight\v4.0\Libraries\Client\System.ComponentModel.Composition.dll. But remember to set the “Copy Local” option to false, as you don’t want to package this assembly with the extensions Xap. It is already loaded by the shell, so there is no need to download it twice… Next, add an ExportAttribute to the code-behind classes of the clocks. The attribute is available in the namespace System.ComponentModel.Composition.

The Export attribute takes a contractType, which is a definition of what type it is exporting. That it, what type we should request when we want an instance of it. In this case, the contractType is ApplicationExtension. As we are exporting 2 different classes with the same type, we need to give them a contract name as well. Otherwise, MEF will just return an instance of one of them twice. I simple name them after their type…

[Export("DarksideCookie.ExtensibilityDemo.MEF.Extensions.DigitalClock", typeof(ApplicationExtension))]
public partial class DigitalClock : ApplicationExtension
...
[Export("DarksideCookie.ExtensibilityDemo.MEF.Extensions.AnalogClock", typeof(ApplicationExtension))]
public partial class AnalogClock : ApplicationExtension

 

Now…if they had been in the same Xap as my shell, I could easily have got hold of them by adding a property on my Shell class like this

[ImportMany]
public ApplicationExtension[] Extensions
{
get;
set;
}

 

Ok…so that is quite simple really. But that isn’t what I am going to do as the extensions aren’t in the same Xap. But before I show you what I am going to do, we have to cover one more thing, the Location property. In the previous post, I used a property on the ApplicationExtension class to tell the Shell where to place it. In MEF, you generally do something else. Since that is really metadata about the extension, it should be treated as such. In MEF, metadata is handled in a really cool, but to me somewhat confusing initially. Just bare with me, and you will see the coolness unfold.

I create a new class in the MEF.Common project and call it ApplicationExtensionMetaDataAttribute. I have it inherit from ExportAttribute (available in the System.ComponentModel.Composition namespace). Next, the constructor must call the base class’ constructor, passing in the type that the metadata is about. In my case, that is obviously ApplicationExtension.

public class ApplicationExtensionMetaDataAttribute : ExportAttribute
{
public ApplicationExtensionMetaDataAttribute() : base(typeof(ApplicationExtension))
{

}
...
}

I mark the class with a MetadataAttribute attribute as well as a AttributeUsage attribute. The latter indicating that the target type is Class and that it does not allow multiple attributes

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ApplicationExtensionMetaDataAttribute : ExportAttribute
...

Next up is the actual data. I declare an enum called Location, and give it two values – Left and Right. After that, I declare a read/write property of type Location on my ApplicationExtensionMetaDataAttribute class. Here is the completed thing

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ApplicationExtensionMetaDataAttribute : ExportAttribute
{
public ApplicationExtensionMetaDataAttribute() : base(typeof(ApplicationExtension))
{

}

public Location Location
{
get;
set;
}
}

public enum Location
{
Left,
Right
}

This part is not that complicated or confusing. All I have to do is adorn our extensions with this attribute. For example

[Export("DarksideCookie.ExtensibilityDemo.MEF.Extensions.AnalogClock", typeof(ApplicationExtension))]
[ApplicationExtensionMetaData(Location=Location.Left)]
public partial class AnalogClock : ApplicationExtension
...

The next part is what I find slightly confusing, even if I sort of understand it…

I declare an interface that I call IApplicationExtensionMetaData in the MEF.Common project. It exposes a single read-only property of the same type and with the same name as the property in the ApplicationExtensionMetaDataAttribute, that is a read-only property of type Location with the name Location.

public interface IApplicationExtensionMetaData
{
Location Location
{
get;
}
}

After that, I just leave it there to simmer…

Now to the part where I start stealing from Glen… He shows a great way to abstract away some of the Xap import functionality in a nice service, and then use MEF to get to the service. Very nice… Glen’s version is more powerful and has some more features, but I only took the tiny part that I needed. It looks like this…

I start off by adding a new interface called IDeploymentCatalogService in the shell project. It defines a single method, AddXap(). The AddXap() method takes a single parameter, a string with the URL to the Xap to download.

public interface IDeploymentCatalogService
{
void AddXap(string uri);
}

The implementation declares a static AggregateCatalog. A catalog is basically a list of exported types that MEF has found. The AggregateCatalog can aggregate types from other catalogs. In the static constructor, the AggregateCatalog is instantiated and an empty DeploymentCatalog is added to it. Next, the CompositionHost, which is responsible for the handling of extensions, is initialized using the AggregateCatalog.

public class DeploymentCatalogService : IDeploymentCatalogService
{
private static AggregateCatalog _aggregateCatalog;

public static void Initialize()
{
_aggregateCatalog = new AggregateCatalog();
_aggregateCatalog.Catalogs.Add(new DeploymentCatalog());
CompositionHost.Initialize(_aggregateCatalog);
}

...
}

As you can see, the class implements IDeploymentCatalogService, and must therefore implement the AddXap method. It does so simply by instantiating a new DeploymentCatalog, telling it to download the Xap and then add the catalog to the AggregateCatalog.

public void AddXap(string uri)
{
DeploymentCatalog catalog = new DeploymentCatalog(uri);
catalog.DownloadAsync();
_aggregateCatalog.Catalogs.Add(catalog);
}

Finally, which is the really neat part. I adorn the DeploymentCatalogService with an Export attribute, exporting IDeploymentCatalogService

[Export(typeof(IDeploymentCatalogService))]
public class DeploymentCatalogService : IDeploymentCatalogService
...

 

Why? Well, you will se in a minute…

Let’s head back to the code behind of the MainPage.xaml. Previously, I added some functionality to the constructor of it, let’s look at the continuation of the code in the ExtensionsReceived() method. The next line after the error check looks like this

CompositionInitializer.SatisfyImports(this);

 

This tells MEF to make sure that any property marked with an Import attribute, gets set with a type that exports the imported type. But, the MainPage doesn’t have any properties. No, not yet…but I am about to add one of two that needs to be added. I start by adding one that looks like this

[Import]
public IDeploymentCatalogService CatalogService { get; set; }

 

This is the cool part. As the DeploymentCatalogService exports IDeploymentCatalogService, it will be used to populate this property when the SatisfyImports() method is called. But the nice thing is that the code has no idea how it is implemented as everything is abstracted away with an interface…

After the call to the SatisfyImports() method in the ExtensionsReceived() method, I add a little foreach loop that goes through the Xap Urls returned by the service and tell the CatalogService to download them.

void ExtensionsReceived(object sender, ExtensionService.GetMEFExtensionLibrariesCompletedEventArgs e)
{
if (e.Error != null)
return;

CompositionInitializer.SatisfyImports(this);
foreach (var ext in e.Result)
CatalogService.AddXap(ext);
}

 

So, where do I actually get hold of the extensions? Well, the code I have written so far, will download the Xaps and parse them for exported types, but never actually use the exported types. As you might have understood by now, you will need another property that the CompositionInitializer can set when new extensions are found. In this case, i am going to use a new type from .NET 4 that is called Lazy<>. It will not initiate the actual object until it really has to (lazy loading). By exposing an array of these objects and marking it with an import attribute, MEF has somewhere to put the found extensions. Since we are loading more than one instance, we have to use the ImportsMany attribute.

[ImportMany(AllowRecomposition = true)]
public Lazy<ApplicationExtension, IApplicationExtensionMetaData>[] Extensions
{
get;
set;
}

 

There are a couple of things that might not be too obvious here. Let’s start by looking at the attribute. As you can see, it sets “AllowRecomposition” to true. This means that MEF is allowed to set this property multiple times when new exports are found. in this example, it will first be set to an empty array when the SatisfyImports() method is called, and then set another time when the extensions Xap has been downloaded and new exports have been found.

Next, you might have noticed that the Lazy<> type uses 2 types, ApplicationExtension and IApplicationExtensionMetaData. As you have seen before, MEF handles metadata using interfaces. By declaring a Lazy<> with the import type as well as the metadata type to use, MEF will populate both on when it finds an exported type. It does so by creating a proxy class that implements the metadata interface on the fly for us.

Ok, so now we know that the Extensions property will be set when MEF finds exports that satisfies our import. But how do we know that the Extensions property has been set? Should I add functionality in the setter of the property to handle this? No…not really… The System.ComponentModel.Composition namespace contains an interface called IPartImportsSatisfiedNotification. If a class that has imports declared implements this interface, MEF will automatically call its only defined method, OnImportsSatisfied(). So I let the MainPage class implement this interface.

 

public partial class MainPage : UserControl, IPartImportsSatisfiedNotification
{
...
public void OnImportsSatisfied()
{
...
}
}

All I have to do in this method is to make sure that all ApplicationExtensions in the Extensions property have been added to the Grid. And also make sure that any extension that hasn’t been added is added in the right column. The ApplicationExtension is found in the Lazy<> objects Value property, and the metadata implementaion is in the MetaData property. So the implementation looks like this

public void OnImportsSatisfied()
{
foreach (var extension in Extensions)
{
ApplicationExtension ext = extension.Value;
if (LayoutRoot.Children.Contains(ext))
return;

Grid.SetColumn(ext, extension.Metadata.Location == Location.Left ? 0 : 1);
LayoutRoot.Children.Add(ext);
}
}

 

That’s all there is to it. Running this application will do exactly the same thing as my custom implementation from the previous post.

So what are the benefits? Well, it automates a few things for us. But to be honest, in this very simple scenario there is very little benefit according to me. I like the automagically imported types and so, but I think the project needs to be bigger to show the real benefits of MEF. I also think that it is something I would use if I used MEF for other parts of the application as well. Adding MEF just to handle plug-in loading in Silverlight feels a bit overkill. Besides, it actually adds quite a bit to the download size. My custom shell Xap was 11kb and the MEF one is 125kb. The extensions Xap is also a tad bit larger. But as I said before, if I was working on a larger project that could leverage more of the MEF framework, I would definitely use it. It has some powerful features. I would however like to see it broken into smaller pieces somehow, so that I would limit the size of my download and only “pay” for the parts I used…

I might even go so far as to say that MEF would be a better fit in desktop application or server side for web. I think that a lot of the features I like could just as well be handled by Unity, which is a lot smaller. But I want to be honest and say that I haven’t looked enough at MEF to give you a really qualified answer. I suggest having a look at Glen’s blog for more information about MEF, and also posting some comments below if you feel that there is something I should know or something I have done wrong…

Here is the code for download: DarksideCookie.ExtensibilityDemo.zip (91.42 kb)

Cheers!

Comments are closed