Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Simplified MEF: Dynamically Loading a Silverlight .xap

0.00/5 (No votes)
6 Jun 2010 3  
How to use The Managed Extensibility Framework to Dynamically Load a Silverlight .XAP using View Model Style
imgBC.jpg

MEF Simplified: Using The Managed Extensibility Framework to Dynamically Load a Silverlight .XAP

Live Example: http://silverlight.adefwebserver.com/dynamicMEF

The documentation for The Managed Extensibility Framework (MEF) describes its purpose as a tool that "...simplifies the creation of extensible applications". That is a very general statement. It's like describing nuclear physics as "the field of physics that studies the building blocks and interactions of atomic nuclei". In some ways MEF is as powerful to programming as nuclear physics is to science.

However, for the "common man", nuclear physics is only important because it makes your lights turn on when you flick the light switch. With MEF, we will use it to simply load a .xap file that contains a Silverlight application. Trust me, it can do a lot more, but MEF is in a state like the Visual State Manager was years ago, powerful but hard to use, so you may decide to wait for more advanced tooling to appear before implementing some of it's other functions.

A Quick Easy Win - For Those Having A Hard Time Understanding MEF

You may have heard about MEF and how great it is. However, you may find it a bit confusing and hard to follow. Fear not. This article is designed for you! The goal here is to give you a quick easy win. You will be able to understand this one. You will come away with something you can use, and you will be able to tell your fellow colleagues, "MEF? yeah I'm using that, good stuff".

A Little Help From My Friends - Glenn Block and Davide Zordan

This article covers material, and uses code, from Glenn Block and Davide Zordan. You will want to read their articles after this one because they go much deeper into what you can do with MEF. The difference with this article is that we will walk through all the steps and we will also use View Model Style for everything.

If you are new to View Model Style it is suggested that you read Silverlight View Model Style : An (Overly) Simplified Explanation for an introduction.

The Application

First, let us walk though the completed application:

First, we add two Silverlight applications to the ClientBin directory of the web project for the Main Silverlight Project.

When you launch the Main Silverlight Project, you can select a Silverlight .xap from the dropdown, and click the Load button.

The .xap will be dynamically loaded.

Creating the "Injected" Silverlight Applications

We will first create two Silverlight applications. They will each produce a .XAP file that will be placed in the ClientBin directory of the Main Silverlight Project. The Main Silverlight project with then dynamically load them, and show them.

Open Microsoft Expression Blend 4, and select File then New Project (We are using Expression Blend because it has templates to easily create View Model style controls) all of this code will of course work with Visual Studio (I actually use Visual Studio to write all the code orginally).

Create a new Silverlight Application called InjectedApplication1

In the Projects window, right-click on References and select Add Reference...

Navigate to:

..\Program Files\Microsoft SDKs\Silverlight\v4.0\Libraries\Client\

Add references to:

  • System.ComponentModel.Composition.dll
  • System.ComponentModel.Composition.Initialization.dll

(you need these references, so we can add MEF attributes to the class, so the application can be recognized by MEF)

Additional Notes:

  • You don't need System.ComponentModel.Composition.Initialization.dll to be referenced by your Silverlight Applications (part assemblies) unless they are using CompositionInitializer.
  • For part assemblies you should set the MEF references (and other shared references) to be CopyLocal = false.
  • CopyLocal = false will keep the size of the XAPs down, it will also prevent duplicates

Delete the MainPage.xaml page.

Select File then New Item...

Create a UserControl with ViewModel called MainPage.xaml.

Open the MainPageModel.cs file.

Replace ALL the code with the following code:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Data;
using System.ComponentModel;

namespace InjectedApplication1
{
    public class MainPageModel : INotifyPropertyChanged
    {
        public MainPageModel()
        {
            Hello = "Hello From InjectedApplication1";
        }

        #region Hello
        private String _Hello;
        public String Hello
        {
            get { return _Hello; }
            private set
            {
                if (Hello == value)
                {
                    return;
                }

                _Hello = value;
                this.NotifyPropertyChanged("Hello");
            }
        }
        #endregion

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }
        #endregion
    }
}

Open MainPage.xaml.cs.

Replace ALL the code with the following code:

using System.ComponentModel.Composition;
using System.Windows.Controls;

namespace InjectedApplication1
{
    [Export(typeof(UserControl))]
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            this.InitializeComponent();
        }
    }
}

(note we are adding the attribute "[Export(typeof(UserControl))]" to the class to allow it to be recognized by MEF)

Open MainPage.xaml.

Click on the UserControl in the Objects and Timeline window.

In the Properties, set the Layout Width and Height to 200.

Click the New button next to DataContext

Select MainPageModel and click OK.

  • Click on the Data tab
  • Under Data Context, click and drag Hello: (String) to the design surface

(note, if the Data Context is empty, build the project, close MainPage.xaml and re-open it)

The text will show.

From the Project tab, select Build Project.

In the file manager on your computer, when you look in:

...\InjectedApplication1\InjectedApplication1\Bin\Debug

You will see:

InjectedApplication1.xap

Later, you will drop it into the ClientBin directory of the Main Silverlight Application to be dynamically injected.

Repeat the process to create another Silverlight Application called InjectedApplication2.

(note: you can download the .xap's from this link: InjectedApplicationXAPs.zip)

You can also hit F5 to build and run the project. It is basically a normal Silverlight project with a few References, using statements, and an attribute added.

The Main Silverlight Application

We are now ready to create the Main Silverlight application that will use MEF to dynamically inject the two Silverlight Applications that we just created.

Create a new Silverlight Application + Website called MainApplication

Add references to:

  • System.ComponentModel.Composition.dll
  • System.ComponentModel.Composition.Initialization.dll
  • System.Windows.Interactivity

(note: Get System.Windows.Interactivity.dll from ..\Program Files\Microsoft SDKs\Expression\Blend\Silverlight\v4.0\Libraries\)

(note: System.Windows.Interactivity.dll is used to make the behaviors (used later) work. It is not part of MEF)

Delete the MainPage.xaml page.

Create a UserControl with ViewModel called MainPage.xaml.

Add a new folder and call it HelperClasses.

ICommand Support

Add a class to the HelperClasses folder called DelegateCommand.cs.

Replace ALL the code with the following code:

using System.Windows.Input;
using System;

namespace MainApplication
{
    // From http://johnpapa.net/silverlight/5-simple-steps-to-commanding-in-silverlight/
    public class DelegateCommand : ICommand
    {
        Func<object, bool> canExecute;
        Action<object> executeAction;
        bool canExecuteCache;

        public DelegateCommand(Action<object> executeAction, Func<object, bool> canExecute)
        {
            this.executeAction = executeAction;
            this.canExecute = canExecute;
        }

        #region ICommand Members

        public bool CanExecute(object parameter)
        {
            bool temp = canExecute(parameter);

            if (canExecuteCache != temp)
            {
                canExecuteCache = temp;
                if (CanExecuteChanged != null)
                {
                    CanExecuteChanged(this, new EventArgs());
                }
            }

            return canExecuteCache;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            executeAction(parameter);
        }

        #endregion
    }
}

This class allows us to easily invoke ICommands. You can get more information on this class at: http://johnpapa.net/silverlight/5-simple-steps-to-commanding-in-silverlight/

Deployment Catalog Service (COMPLICATED STUFF!)

We are now getting into the "complicated stuff". We are going to create a class that will actually load the .xap on demand. We will create an Interface for it's methods; AddXap and RemoveXap, and then we will implement the class (we are putting both in the same file to simply things, normally you would put your interface in it's own file).

Add a class to the HelperClasses folder called DeploymentCatalogService.cs.

Replace ALL the code with the following code:

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel;

namespace MainApplication
{
    // A type from which to derive the contract name that is used to export the
    // type or member marked with this attribute,
    public interface IDeploymentCatalogService
    {
        // The class that will implement this interface will implement these two methods
        void AddXap(string uri, Action<AsyncCompletedEventArgs> completedAction = null);
        void RemoveXap(string uri);
    }

    [Export(typeof(IDeploymentCatalogService))]
    public class DeploymentCatalogService : IDeploymentCatalogService
    {
        private static AggregateCatalog _aggregateCatalog;
        Dictionary<string, DeploymentCatalog> _catalogs;

        public DeploymentCatalogService()
        {
            _catalogs = new Dictionary<string, DeploymentCatalog>();
        }

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

        public void AddXap(string uri, Action<AsyncCompletedEventArgs> completedAction = null)
        {
            // Add a .xap to the catalog
            DeploymentCatalog catalog;
            if (!_catalogs.TryGetValue(uri, out catalog))
            {
                catalog = new DeploymentCatalog(uri);

                if (completedAction != null)
                {
                    catalog.DownloadCompleted += (s, e) => completedAction(e);
                }
                else
                {
                    catalog.DownloadCompleted += catalog_DownloadCompleted;
                }

                catalog.DownloadAsync();
                _catalogs[uri] = catalog;
                _aggregateCatalog.Catalogs.Add(catalog);
            }
        }

        void catalog_DownloadCompleted(object sender, AsyncCompletedEventArgs e)
        {
            // Chekcks for errors loading the .xap
            if (e.Error != null)
            {
                throw e.Error;
            }
        }

        public void RemoveXap(string uri)
        {
            // Remove a .xap from the catalog
            DeploymentCatalog catalog;
            if (_catalogs.TryGetValue(uri, out catalog))
            {
                _aggregateCatalog.Catalogs.Remove(catalog);
                _catalogs.Remove(uri);
            }
        }
    }
}

For a full explanation on this class, see this article and this article.

Yes we could go into the details of "how" it does what it does, but that is covered in the links above. If you want to know "what" it does, it loads a requested .xap into a "catalog". The code we will cover next, pulls the .xap out of the catalog and loads it into the Main Silverlight Application.

Dynamically Downloading a Silverlight Application Using MEF

We will now create the View Model. It will basically perform the function in the diagram above.

Open up MainPageModel.cs and replace ALL the code with the following code:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Data;
using System.ComponentModel;
using System.Windows.Input;
using System.ComponentModel.Composition;
using System.Windows.Controls;
using System.Linq;
using System.Collections.ObjectModel;

namespace MainApplication
{
    public class MainPageModel : INotifyPropertyChanged, IPartImportsSatisfiedNotification
    {
        private string strSelectedXAP = "";
        public MainPageModel()
        {
            // Set the command properties
            LoadXAPCommand = new DelegateCommand(LoadXAP, CanLoadXAP);
            SetMEFPanelCommand = new DelegateCommand(SetMEFPanel, CanSetMEFPanel);

            // The following line prevents Expression Blend
            // from showing an error when in design mode
            if (!DesignerProperties.IsInDesignTool)
            {
                // This call causes DeploymentCatalogService() to be called
                CompositionInitializer.SatisfyImports(this);
            }

        }

        #region Commanding
        public ICommand LoadXAPCommand { get; set; }

        public void LoadXAP(object param)
        {
            // Get the .xap selected
            ComboBoxItem objComboBoxItem = (ComboBoxItem)param;
            strSelectedXAP = objComboBoxItem.Content.ToString();

            // Have MEF load the .xap
            CatalogService.AddXap(strSelectedXAP);

            // If the .xap is already loaded we need to call this method
            // because OnImportsSatisfied() will not fire
            LoadSelectedModule();
        }

        private bool CanLoadXAP(object param)
        {
            return true;
        }

        public ICommand SetMEFPanelCommand { get; set; }
        public void SetMEFPanel(object param)
        {
            MefPanel = (Panel)param;
        }

        private bool CanSetMEFPanel(object param)
        {
            // Must be a Panel
            return ((param as Panel) != null);
        }
        #endregion

        // Specifies that a property, field, or parameter value should be provided by
        // the System.ComponentModel.Composition.Hosting.CompositionContainer.
        [Import]
        public IDeploymentCatalogService CatalogService { get; set; }

        // Specifies that a property, field, or parameter should be populated with all
        // matching exports by the System.ComponentModel.Composition.Hosting.CompositionContainer.
        [ImportMany(AllowRecomposition = true)]
        public Lazy<UserControl>[] MEFModuleList { get; set; }

        #region IPartImportsSatisfiedNotification Members
        // This fires when a .xap has been loaded
        // If the .xap has already been loaded this will not fire
        public void OnImportsSatisfied()
        {
            LoadSelectedModule();
        }
        #endregion

        #region LoadSelectedModule
        private void LoadSelectedModule()
        {
            // Ensure that we have a Panel to add the .xap to
            if (MefPanel != null)
            {
                // Create a name for the .xap without the ".xap" part
                string strRevisedSelectedXAPName = strSelectedXAP.Replace(".xap", ".");

                // Determine if the .xap is already loaded
                var SelectedMEFModuleInPanel = (from Module in MefPanel.Children.Cast<UserControl>()
                                                where Module.ToString().Contains(strRevisedSelectedXAPName)
                                                select Module).FirstOrDefault();

                // If the .xap is not loaded
                if (SelectedMEFModuleInPanel == null)
                {
                    // Clear the panel
                    MefPanel.Children.Clear();

                    // Get the selected .xap 
                    var SelectedMEFModule = (from Module in MEFModuleList.ToList()
                                             where Module.Value.ToString().Contains(strRevisedSelectedXAPName)
                                             select Module).FirstOrDefault();

                    // If the .xap is found
                    if (SelectedMEFModule != null)
                    {
                        // Add the .xap to the main page
                        MefPanel.Children.Add(SelectedMEFModule.Value);
                    }
                }
            }
        }
        #endregion

        #region MefPanel
        private Panel _MefPanel;
        public Panel MefPanel
        {
            get { return _MefPanel; }
            private set
            {
                if (MefPanel == value)
                {
                    return;
                }

                _MefPanel = value;
                this.NotifyPropertyChanged("MefPanel");
            }
        }
        #endregion

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }
        #endregion
    }
}

The diagram above shows the class view of the View Model.

We have reached the apex of the "complicated stuff". It's all downhill from here. There are links at the end of this article that will fully explain how the code above works. The general explanation is in the comments in the code.

The View (The UI)

Open MainPage.xaml.

Switch to XAML View.

Replace ALL the code with the following code:

<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MainApplication"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
x:Class="MainApplication.MainPage"
UseLayoutRounding="True"
Width="640" Height="480">
<UserControl.Resources>
<local:MainPageModel x:Key="MainPageModelDataSource" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource MainPageModelDataSource}}">
<TextBlock HorizontalAlignment="Left" Height="40" Margin="8,8,0,0" TextWrapping="Wrap" Text="Main Page"
VerticalAlignment="Top" Width="168" FontSize="32" Foreground="#FFC82929"/>
<Button Content="Load" HorizontalAlignment="Right" Height="24" Margin="0,24,144,0" 
VerticalAlignment="Top" Width="96" />
<StackPanel x:Name="MEFContent" Margin="12,56,16,16"/>
<ComboBox x:Name="comboBox" VerticalAlignment="Top" Margin="192,28,256,0" SelectedIndex="0">
<ComboBoxItem Content="InjectedApplication1.xap"/>
<ComboBoxItem Content="InjectedApplication2.xap"/>
</ComboBox>
</Grid>
</UserControl>

Switch back to Design View and you will see the UI.

Wire It Up! - The Panel

We need to connect the View to the View Model by registering the StackPanel as the place to inject the Silverlight applications, and the Load button to load the Silverlight Applications.

Get an InvokeCommandAction Behavior.

Drop it on the LayoutRoot in the Objects and Timeline window.

In the Properties for the Behavior:

  • Select Loaded for EventName (this means the behavior will run when the application starts)
  • Click the Data bind icon next to Command (under Common Properties)

Select SetMEFPanelCommand and click OK.

Select Advanced options for CommandParameter (we now need to indicate exactly what Panel element we want the Silverlight Applications to be placed inside of)

Select Data Binding...

Select the MEFContent StackPanel (on the Element Property tab) and click OK.

Wire It Up! - The Load Button

Drop an InvokeCommandAction Behavior on the [Button]

In the Properties for the Behavior:

  • Select Click for EventName (the Behavior will ruin when the Load button is clicked)
  • Click the Data bind icon next to Command (under Common Properties)

Bind to LoadXAPCommand

Select Advanced options for CommandParameter and then select Element Property Binding...

Click on the comboBox in the Objects and Timeline window

Select SelectedItem in the Property of comboBox dropdown and click OK.

The diagram above shows what is now wired to what.

One More Thing - Initialize the DeploymentCatalogService

Open the App.xaml.cs file.

Change the Application_Startup method to:

    private void Application_Startup(object sender, StartupEventArgs e)
    {
        DeploymentCatalogService.Initialize();
        this.RootVisual = new MainPage();
    }

We need to add the code "DeploymentCatalogService.Initialize()" to initialize the DeploymentCatalogService, for it to work.

Add the Silverlight Applications

When you look in the website project in the Projects window, you will only see the MainApplication.xap file in the ClientBin directory.

Drag and drop the InjectedApplication1.xap and InjectedApplication2.xap files into the directory.

Hit F5 to build and run the application.

You will be able to dynamically load each application.

Why Should You Use MEF To Dynamically Load Silverlight Applications ?

There are other ways to dynamically load a Silverlight .xap. I have a project (Introducing SilverlightDesktop.net) that does that. the problem I ran into, was handling the project dependencies. For example, if you use a control from the Silverlight Control Toolkit, it wont work. MEF will resolve any dependencies. This is really important when using View Model Style. A Designer could add a lot of custom Behaviors and other dependencies.

This is an example where using MEF is LESS code than if you used an alternative method (that provided the same powerful functionality). The hard part with MEF is putting the correct attributes in the correct places and setting their values properly. Hopefully this tutorial provided a helpful example.

There Is More To MEF

There is so much more to MEF, that this simple example is almost insulting to the technology. However, I fully expect Microsoft to create more tooling and example code to make it's implementation of it's other features easier in the future.

A special thanks to Glenn Block for help with this article.

Further Reading

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here