Click here to Skip to main content
15,860,859 members
Articles / Programming Languages / C# 4.0

Simplified MEF: Dynamically Loading a Silverlight .xap

Rate me:
Please Sign up or sign in to vote.
4.94/5 (36 votes)
6 Jun 2010Ms-PL9 min read 224.8K   2.6K   77   51
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

Image 2

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:

Image 3

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

Image 4

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

Image 5

The .xap will be dynamically loaded.

Creating the "Injected" Silverlight Applications

Image 6

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.

Image 7

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).

Image 8

Create a new Silverlight Application called InjectedApplication1

Image 9

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

Image 10

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

Image 11

Delete the MainPage.xaml page.

Image 12

Select File then New Item...

Image 13

Create a UserControl with ViewModel called MainPage.xaml.

Image 14

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
    }
}

Image 15

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)

Image 16

Open MainPage.xaml.

Image 17

Click on the UserControl in the Objects and Timeline window.

Image 18

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

Image 19

Click the New button next to DataContext

Image 20

Select MainPageModel and click OK.

Image 21

  • 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)

Image 22

The text will show.

Image 23

From the Project tab, select Build Project.

Image 24

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)

Image 25

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

Image 26

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.

Image 27

Create a new Silverlight Application + Website called MainApplication

Image 28

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)

Image 29

Delete the MainPage.xaml page.

Create a UserControl with ViewModel called MainPage.xaml.

Image 30

Add a new folder and call it HelperClasses.

ICommand Support

Image 31

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!)

Image 32

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

Image 33

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

Image 34

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
    }
}

Image 35

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)

Image 36

Open MainPage.xaml.

Image 37

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>

Image 38

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.

Image 39

Get an InvokeCommandAction Behavior.

Image 40

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

Image 41

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)

Image 42

Select SetMEFPanelCommand and click OK.

Image 43

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

Image 44

Select Data Binding...

Image 45

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

Wire It Up! - The Load Button

Image 46

Drop an InvokeCommandAction Behavior on the [Button]

Image 47

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)

Image 48

Bind to LoadXAPCommand

Image 49

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

Image 50

Click on the comboBox in the Objects and Timeline window

Image 51

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

Image 52

The diagram above shows what is now wired to what.

One More Thing - Initialize the DeploymentCatalogService

Image 53

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

Image 54

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

Image 55

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

Image 56

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, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Software Developer (Senior) http://ADefWebserver.com
United States United States
Michael Washington is a Microsoft MVP. He is a ASP.NET and
C# programmer.
He is the founder of
AiHelpWebsite.com,
LightSwitchHelpWebsite.com, and
HoloLensHelpWebsite.com.

He has a son, Zachary and resides in Los Angeles with his wife Valerie.

He is the Author of:

Comments and Discussions

 
QuestionHow to use EventAggregator with the child usercontrol Pin
josephSurgeon17-May-13 11:46
josephSurgeon17-May-13 11:46 
AnswerRe: How to use EventAggregator with the child usercontrol Pin
defwebserver17-May-13 11:53
defwebserver17-May-13 11:53 
GeneralRe: How to use EventAggregator with the child usercontrol Pin
josephSurgeon17-May-13 11:58
josephSurgeon17-May-13 11:58 
GeneralRe: How to use EventAggregator with the child usercontrol Pin
defwebserver17-May-13 12:08
defwebserver17-May-13 12:08 
GeneralRe: How to use EventAggregator with the child usercontrol Pin
josephSurgeon20-May-13 2:58
josephSurgeon20-May-13 2:58 
GeneralRe: How to use EventAggregator with the child usercontrol Pin
josephSurgeon21-May-13 7:00
josephSurgeon21-May-13 7:00 
GeneralRe: How to use EventAggregator with the child usercontrol Pin
defwebserver21-May-13 7:15
defwebserver21-May-13 7:15 
QuestionConcerning error handling... Pin
frantic016-Feb-11 6:02
professionalfrantic016-Feb-11 6:02 
AnswerRe: Concerning error handling... Pin
defwebserver16-Feb-11 9:12
defwebserver16-Feb-11 9:12 
GeneralRe: Concerning error handling... Pin
frantic016-Feb-11 9:49
professionalfrantic016-Feb-11 9:49 
GeneralRe: Concerning error handling... Pin
defwebserver16-Feb-11 10:00
defwebserver16-Feb-11 10:00 
QuestionGetting error on LoadSelectedModule() Pin
dtdono010-Nov-10 17:21
dtdono010-Nov-10 17:21 
AnswerRe: Getting error on LoadSelectedModule() Pin
defwebserver10-Nov-10 17:44
defwebserver10-Nov-10 17:44 
GeneralBrilliant! Pin
Kemo720005-Nov-10 15:36
Kemo720005-Nov-10 15:36 
GeneralRe: Brilliant! Pin
defwebserver5-Nov-10 16:29
defwebserver5-Nov-10 16:29 
GeneralSelecting specific usercontrol within an xap file Pin
webtecs9-Sep-10 1:44
webtecs9-Sep-10 1:44 
GeneralRe: Selecting specific usercontrol within an xap file Pin
defwebserver9-Sep-10 2:18
defwebserver9-Sep-10 2:18 
GeneralRe: Selecting specific usercontrol within an xap file Pin
webtecs9-Sep-10 22:32
webtecs9-Sep-10 22:32 
GeneralRe: Selecting specific usercontrol within an xap file Pin
defwebserver10-Sep-10 2:59
defwebserver10-Sep-10 2:59 
GeneralThx a lot Pin
Ingo Schelfhout26-Aug-10 22:32
Ingo Schelfhout26-Aug-10 22:32 
GeneralRe: Thx a lot Pin
defwebserver27-Aug-10 2:14
defwebserver27-Aug-10 2:14 
GeneralMy vote of 5 Pin
ksafford18-Aug-10 5:34
ksafford18-Aug-10 5:34 
GeneralRe: My vote of 5 Pin
ksafford18-Aug-10 7:29
ksafford18-Aug-10 7:29 
GeneralRe: My vote of 5 Pin
defwebserver18-Aug-10 8:00
defwebserver18-Aug-10 8:00 
GeneralRe: My vote of 5 Pin
ksafford18-Aug-10 8:22
ksafford18-Aug-10 8:22 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.