Wintellect  

Browse by Tags

All Tags » silverlight 3   (RSS)

Recently I've been having lots of conversations about the Managed Extensibility Framework (MEF), the Composite Application Library (CAL or PRISM), and how they relate. One point of confusion that many people has comes when they try to force the two solutions to work together. In a recent conversation, I mentioned that PRISM has some great features, but that if you are only using it for dynamic module loading and view management, MEF should do fine. Then I promised to post a blog with a reference project ... and here it is.

Download the source for this project

First, let me share that I love PRISM and have been working with it in almost all of my projects for the past year. My Wintellect colleague Rik Robinson has an excellent article on PRISM you can read here. You can also scan this blog with the PRISM tag. However, I've started to really enjoy working with MEF and believe it is quickly becoming the solution of choice for composite Silverlight applications ... especially with it's inclusion in the upcoming 4.0 release. In these 2 posts I'll show you how to tackle dynamic module loading and region management using exclusively MEF instead of PRISM.

I'm working with the preview 9 of the bits and showing what can be done in the current production release of Silverlight which is version 3. To start with, I simply create a new Silverlight Application. I add a folder called "MEF" and throw the bits in there, which is really two DLLs: System.ComponentModel.Composition and System.ComponentModel.Composition.Initialization. I reference those.

The "Bootstrapper" in MEF

Next I create a new empty user control called Shell.xaml (sound familiar?) that just has a grid and a text block so I know it's there. If you are familiar with PRISM, you are familiar with the concept of the Bootstrapper class to wire everything up. With MEF, we'll do it a little different. First, I go inside the shell and simply decorate it with the "export" attribute:

[Export]
public partial class Shell
{
    public Shell()
    {
        InitializeComponent();
    }
}

Next, I go into my main application class (App.cs) and add a property to import the Shell, like this:

...
[Import]
public Shell RootView { get; set; }
...

Finally, in the application start-up method, instead of newing up a "main page" which no longer exists, I simply ask MEF to satisfy my import of the shell, then assign it to the root visual:

private void Application_Startup(object sender, StartupEventArgs e)
{
    CompositionInitializer.SatisfyImports(this);
    RootVisual = RootView;
}

That's it! When I hit F5 I see the text I placed in the shell, so I know it's being placed in the root visual. We're good to go! I'm going to tackle dynamic loading first, then look at region management. I'll need some buttons to trigger the loading, so we'll want to roll a command behavior. This is one of the nice things that comes with PRISM (the commands), but let's see how easy or difficult it is to roll our own.

Custom Commands

Microsoft Expression Blend provides a set of base triggers and behaviors that make it easy to add new functionality like commanding. If you don't have the full tool, don't despair - these are also included in the free SDK you can download. I'm including a reference to System.Windows.Interactivity.

I want to do a trigger action. A trigger action allows me to act on any event, which I prefer over hard-coding just to a button click.

Before I get ahead of myself, though, let's create our command. While Silverlight 3 doesn't have a built-in command object, it does provide the ICommand interface. I'm going to do a partial implementation. I say "partial" because the interface allows for something to be passed to the command, and I'm simply raising the command and assuming null for the sake of this blog post:

public class CommandAction : ICommand 
{
    private readonly Func<bool> _canExecute;

    private readonly Action _execute; 

    public CommandAction(Action action, Func<bool> canExecute)
    {
        _execute = action;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        return _canExecute();
    }

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

    public void RaiseCanExecuteChanged()
    {
        EventHandler handler = CanExecuteChanged;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
    }

    public event EventHandler CanExecuteChanged;
}

While I haven't looked at the code in PRISM, I assume this is very close to how they wire the DelegateCommand. We hold a function that determines if the command is enabled, and an action to call when the command is invoked. Whoever creates the CommandAction is responsible for passing those delegates in and raising "can execute changed" when appropriate.

Now I can start to work on the command behavior. Because of my unique command type, I'm just going to allow you to pass the name of the command on the data bound object and will dissect the rest. If you bind a view model with "ActionCommand" then I'll let you wire up it up that way. Because we're using MEF to bind our view models, my behavior will get wired well before the bindings happen. I'll need to know when the data context changes, so I build a data context helper. It basically uses dependency properties to listen to a "dummy" binding for changes, then calls an action when the changes happen. It looks like this and is loosely based on a previous post I made on data context changed events:

public static class DataContextChangedHandler
{
    private const string INTERNAL_CONTEXT = "InternalDataContext";
    private const string CONTEXT_CHANGED = "DataContextChanged";

    public static readonly DependencyProperty InternalDataContextProperty =
        DependencyProperty.Register(INTERNAL_CONTEXT,
                                    typeof(Object),
                                    typeof(FrameworkElement),
                                    new PropertyMetadata(_DataContextChanged));

    public static readonly DependencyProperty DataContextChangedProperty =
        DependencyProperty.Register(CONTEXT_CHANGED,
                                    typeof(Action<Control>),
                                    typeof(FrameworkElement),
                                    null);

    
    private static void _DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        var control = (Control)sender;
        var handler = (Action<Control>) control.GetValue(DataContextChangedProperty);
        if (handler != null)
        {
            handler(control); 
        }
    }

    public static void Bind(Control control, Action<Control> dataContextChanged)
    {
        control.SetBinding(InternalDataContextProperty, new Binding());
        control.SetValue(DataContextChangedProperty, dataContextChanged); 
    }
}

In this case, I'm scoping specifically to a control, which is where I believe the lowest level of "is enabled" gets implemented for controls that can react to changes, so that makes sense for our behavior. When I call bind, I pass it a control and a delegate to call when that control's data context changes. Now we can build in our behavior:

public class CommandBehavior : TriggerAction<Control>
{
    public static readonly DependencyProperty CommandBindingProperty = DependencyProperty.Register(
        "CommandBinding",
        typeof(string),
        typeof(CommandBehavior),
        null);

    public string CommandBinding
    {
        get { return (string)GetValue(CommandBindingProperty); }
        set { SetValue(CommandBindingProperty, value); }
    }

    private CommandAction _action; 

    protected override void OnAttached()
    {
        DataContextChangedHandler.Bind(AssociatedObject, obj=>_ProcessCommand());
    }

    private void _ProcessCommand()
    {
        if (AssociatedObject != null)
        {

            var dataContext = AssociatedObject.DataContext;

            if (dataContext != null)
            {
                var property = dataContext.GetType().GetProperty(CommandBinding);
                if (property != null)
                {
                    var value = property.GetValue(dataContext, null);
                    if (value != null && value is CommandAction)
                    {
                        _action = value as CommandAction;
                        AssociatedObject.IsEnabled = _action.CanExecute(null);
                        _action.CanExecuteChanged += (o, e) => AssociatedObject.IsEnabled = _action.CanExecute(null);

                    }
                }
            }
        }
    }

    protected override void Invoke(object parameter)
    {
        if (_action != null && _action.CanExecute(null))
        {
            _action.Execute(null);
        }
    }      
}

OK, let's step through it. I'm using the System.Windows.Interactivity namespace to define a trigger action, which can be bound to any event. I'm scoping it to a control. When my behavior is attached, I'm binding to the data context changed event. I ask it to call my method to process the command when the data context changes (presumably to bind our command). When that fires, I grab the property that is named in my behavior from the data context, cast it to a command, and wire it up to automatically change whether the host control is enabled based on the CanExecute method of the control. When my behavior is invoked, I check this again and then execute.

ViewModel Glue

There's been a lot of discussion (including in this blog) around how to glue the view model to the view. I personally like to keep it simple and straightforward. Here's a view model stubbed out for the shell that lets me click a button to dynamically load a view. I want to disable the button once clicked so they don't load the view more than once.

[Export]
public class ShellViewModel
{
    public ShellViewModel()
    {
        ViewEnabled = true;
        ViewClick = new CommandAction(_ViewRequested,
                                      () => ViewEnabled);
    }

    private bool _viewEnabled;

    public bool ViewEnabled
    {
        get { return _viewEnabled; }
        set
        {
            _viewEnabled = value;
            
            if (ViewClick != null)
            {
                ViewClick.RaiseCanExecuteChanged();
            }
        }
    }

    public CommandAction ViewClick { get; set; }

    private void _ViewRequested()
    {
        ViewEnabled = false;
    }
}

Note I use a property to determine if the command is enabled, then call a method to process it when fired. We'll add more to that method in a minute. Let's get this into our shell. Because the shell is wired up in the application through the initializer, there is no need to initialize or compose again. This is done recursively. So, let me add a little bit to my shell:

<UserControl x:Class="RegionsWithMEF.Shell"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:command="clr-namespace:RegionsWithMEF.Common.Command;assembly=RegionsWithMEF.Common"
    >
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <TextBlock Text="Main MEF Shell"/>
        <Button Content="Load Dynamic View into Region" Width="Auto" Height="Auto" Grid.Row="1">
            <interactivity:Interaction.Triggers>
                <interactivity:EventTrigger EventName="Click">
                    <command:CommandBehavior CommandBinding="ViewClick"/>
                </interactivity:EventTrigger>
            </interactivity:Interaction.Triggers>
        </Button>
    </Grid>
</UserControl>

Notice how I use the interactivity namespace to define a trigger for the button. The trigger is for the "click" event, but the way we built the behavior, it can easily fire based on other events as well. I bring in my command behavior, and point it to the "ViewClick" property on my view model. A more advanced implementation would turn that into a full blown binding property and allow binding syntax but for now we'll stick with the simple property name.

In the code behind, I wire in my view model:

[Import]
public ShellViewModel ViewModel
{
    get { return LayoutRoot.DataContext as ShellViewModel; }
    set { LayoutRoot.DataContext = value; }
}

That's it! I run the project to test it, and get a nice text block with a button. When I click the button, it disables immediately. Now let's tackle that dynamic view!

Controlling the Catalog and Container

We want to dynamically load some modules, but first we'll need to tweak our container. By default, MEF is going to create a container in our main application and compose parts to it. Unfortunately, that means when we load other modules/xap files, they won't have access to the container! We need to fix this.

First, I'll create a service to expose an AggregateCatalog that I can add other catalogs to. I want to get the catalog and add parts to it:

public interface ICatalogService
{
    void Add(ComposablePartCatalog catalog);

    AggregateCatalog GetCatalog();

}

Next, I will implement this by creating an aggregate catalog. I'm going to assume when the service is created that we want to include the currently loaded assemblies. This assumption may be wrong and down the road we might inject the catalog, but for now we'll iterate the current deployment (set of running assemblies) and pull them in to parse into our catalog:

public class CatalogService : ICatalogService
{
    private read-only AggregateCatalog _catalog = new AggregateCatalog();

    public CatalogService()
    {
        foreach (AssemblyPart ap in Deployment.Current.Parts)
        {
            StreamResourceInfo sri = Application.GetResourceStream(new Uri(ap.Source, UriKind.Relative));

            if (sri != null)
            {
                Assembly assembly = ap.Load(sri.Stream);
                _catalog.Catalogs.Add(new AssemblyCatalog(assembly));
            }
        }
    }    

    public void Add(ComposablePartCatalog catalog)
    {
        _catalog.Catalogs.Add(catalog);
    }

    public AggregateCatalog GetCatalog()
    {
        return _catalog; 
    }
}

Now we just need to tweak the main application. We'll instantiate the service, then tell it how to expose itself (sounds weird, I know, but when I bring in a module, I want it to be able to add itself to the aggregate catalog, so the catalog service must be exported). We'll let MEF know we want to use this specific container moving forward, and then we'll initialize as before. The new method looks like this:

private void Application_Startup(object sender, StartupEventArgs e)
{
    ICatalogService service = new CatalogService();
    var container = new CompositionContainer(service.GetCatalog());
    container.ComposeExportedValue(service);
    CompositionHost.Initialize(container); 
    CompositionInitializer.SatisfyImports(this);
    RootVisual = RootView;
}

Dynamic Loading of Modules

MEF Preview 9 comes with a deployment catalog that gives us what we need. First, I want to hide the implementation details of loading a view. I'll create an enumeration of views that are available (in this case, only one) and an interface to call when I'm ready to navigate to a view.

For now, I want to just grab the view and verify it's loaded. Then we'll wrap up for today and tackle the region management in the next post.

I extended my shell view model to anticipate two views. In fact, to make the example more interesting, I will load the second view into the first view. Therefore, the second button is only enabled once the first button has been clicked, like this:

SecondViewClick = new CommandAction(
    _SecondViewRequested,
    () => !ViewEnabled && SecondViewEnabled);

Here's our views:

public enum ViewType
{
    MainView,
    SecondView
}

And the interface for our view navigation:

public interface INavigation
{
    void NavigateToView(ViewType view);
}

So now I can go back to my view model and wire in the navigation, as well as make the call. Here is the modified code:

[Import]
public INavigation Navigation { get; set; }

private void _ViewRequested()
{
    ViewEnabled = false;
    Navigation.NavigateToView(ViewType.MainView);
}

private void _SecondViewRequested()
{
    SecondViewEnabled = false;
    Navigation.NavigateToView(ViewType.SecondView);
}

Now I will add my modules. As I mentioned, I just want them to load for now. I can deal with the views later. So I create two more Silverlight applications in my solution called "DynamicModule" and then "SecondDynamicModule.cs" ... yes, the second is a typo because I thought I was adding a class, and was too lazy to re-factor it. So there. I imagine when I load a module I'll want it to do perform some "introductory" functions, so let's define an initialization interface:

public interface IModuleInitializer
{
    void InitModule();
}

In both of my dynamic modules, I add a class that implements this interface and export it. I gave them different names to show it's the interface the matters, but here's what one looks like (right now I just return, but I can set the breakpoint there to test when and if the module is loaded and called):

[Export(typeof(IModuleInitializer))]
public class ModuleInitializer : IModuleInitializer
{
    public void InitModule()
    {
        return; 
    }              
}

OK, now I can implement my INavigation interface. We're going to do two things. First, we'll map views to modules so we can dynamically load them using the DeploymentCatalog. Second, we'll import a collection of module initializers. This is the super-cool feature of MEF: recomposition. When we load the module and put it in our main catalog, it will recompose. This should fire a change to our collection of initializers. We can take the new items and call them, and we'll be initialized. Here's what it looks like:

[Export(typeof(INavigation))]
public class ViewNavigator : INavigation 
{        
    [Import]
    public ICatalogService CatalogService { get; set; }

    [ImportMany(AllowRecomposition = true)]
    public ObservableCollection<IModuleInitializer> Initializers { get; set; }

  private readonly List<IModuleInitializer> _modules = new List<IModuleInitializer>();

    private readonly Dictionary<ViewType, string> _viewMap =
        new Dictionary<ViewType, string>
            {
                { ViewType.MainView, "DynamicModule.xap" },
                { ViewType.SecondView, "SecondDynamicModule.cs.xap" }
            };   

    private readonly List<string> _downloadedModules = new List<string>();

    public ViewNavigator()
    {
        Initializers = new ObservableCollection<IModuleInitializer>();
        Initializers.CollectionChanged += Initializers_CollectionChanged;
    }
    
    void Initializers_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems != null)
        {
            foreach(var item in e.NewItems)
            {
                var initializer = item as IModuleInitializer; 
                if (initializer != null)
                {
                    if (!_modules.Contains(initializer))
                    {
                        initializer.InitModule();
                        _modules.Add(initializer);
                    }
                }
            }
        }
    }                                                                                              
  
    public void NavigateToView(ViewType view)
    {
        if (!_downloadedModules.Contains(_viewMap[view]))
        {
            var catalog = new DeploymentCatalog(_viewMap[view]);
            CatalogService.Add(catalog);
            catalog.DownloadAsync();
            _downloadedModules.Add(_viewMap[view]);
        }
    }               
}

Right now the navigate to view isn't very flexible. It loads the catalogs but doesn't do anything with the views. I'm also not handling errors when the XAP isn't loaded. We are tracking modules and xaps that were already loaded, so we don't load them again or re-initialize the module. We'll get to that in the next installment when we wire in regions. For now, we can put a breakpoint in the "return" of the init modules, fire up the application, and click our buttons to watch them get dynamically loaded into the system (you can use Fiddler to watch the packets come over the wire). Pretty exciting!

In the next installment, I'll show you how MEF will handle region management.

Download the source for this project

Jeremy Likness

A new version of Silverlight 3 has been released.

You can download the latest control here. If you are a developer, then you'll want to use the developer installer that is available here.

This version fixes some issues related to hardware acceleration in the graphics processing unit (GPU), certain cases that cause Deep Zoom to take on high CPU, and issues with downloads from Silverlight applications.

Read Microsoft's Knowledge Base article for this release: Description of the update for Silverlight: January 19, 2010.

Jeremy Likness

Series recap:

In the final part of this series, I will show a dynamically loaded module (using PRISM) that takes full advantage of MEF.

Here is a preview of the final product, illustrating the different modules and areas I have pulled together to demonstrate (click for a full resolution view):

Download the Source for this Example

Why even bother with dynamic modules? Dynamic module loading can be a powerful way to build scalable (and more stable) applications in Silverlight. By creating dynamic modules, you ensure the user only loads what they need, when they need it. The dynamic module only comes into play when the user demands a feature that requires the module.

Before we dive into the new MEF-based module, let's break down a few best practices when using dynamically loaded modules:

  • The best option I've found for this so far is to use PRISM's "on demand" functionality for loading modules.
  • Typically, you will want to put your main dependencies in the "main" or "host" Silverlight project.
  • Create a new dynamic module by adding a Silverlight Application project, not a Silverlight Class Library.
  • Use a XAML catalog to reference the dynamic modules. This gives PRISM something to resolve when the new module is requested, but avoids direct dependencies on the satellite modules by the main application.
  • When referencing projects or DLLs that are part of the main application in a satellite module, be sure to set "copy local" to false. This ensures these are not compiled into the XAP, because the module can leverage the fact that the main module has already loaded the dependencies. This leads to very lightweight XAP files.

When everything comes together as planned, you can scan traffic with a tool like Fiddler to see what happens. Take a look at this example. Here, you can clearly see the satellite modules are loaded dynamically. In fact, the MEF module, which displays a list and dynamically displays a child view, weighs in at only 5,000 bytes!

Click here to view the Fiddler snapshot

I started by adding a new Silverlight Application and calling it MEFModule.

The MEF ViewModel

The MEF view model looks like this:


[Export]
public class MoreStuffViewModel : IPartImportsSatisfiedNotification
{        
    private IService _service; 

    [Import]
    public IService Service
    {
        get { return _service; }
        set
        {
            _service = value;
            if (_service != null)
            {
                _service.GetMoreStuff(_ServiceLoaded); 
            }
        }
    }

    [ImportMany("MoreStuff",AllowRecomposition=true)]
    public List<string> ImportedStuff { get; set; }

    [Import]
    public IRegionManager RegionManager { get; set; }

    public DelegateCommand<object> DynamicViewCommand { get; set; }

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

    private ObservableCollection<string> _listOfMoreStuff;

    public MoreStuffViewModel()
    {
        // initialize all lists
        _listOfMoreStuff = new ObservableCollection<string>();
        DynamicViewCommand = new DelegateCommand<object>(o =>
            {
                RegionManager.RegisterViewWithRegion("MainRegion", () => DynamicView);
            });
    }

    private void _ServiceLoaded(List<string> values)
    {
        foreach (string value in values)
        {
            _listOfMoreStuff.Add(value);
        }
    }
       
    public ObservableCollection<string> ListOfMoreStuff 
    {
        get
        {
            return _listOfMoreStuff;
        }

        set
        {
            _listOfMoreStuff = value; 
        }
    }

    #region IPartImportsSatisfiedNotification Members

    public void OnImportsSatisfied()
    {
        foreach (string importedValue in ImportedStuff)
        {
            _listOfMoreStuff.Add(importedValue);
        }
    }

    #endregion
}

I've purposefully loaded this view model with tons of goodies to help understand and take advantage of MEF to its fullest. You'll notice two things right away about the view model: it is exported using the Export attribute, and it implements an interface called IPartImportsSatisfiedNotification. When you implement this interface, MEF will automatically call OnImportsSatisfied, allowing you to react to the new imports.

You'll notice I have a collection called ImportedStuff that imports a contract with a magic string (tip: in a production project this would probably be a type or an enumeration or at least a static class with a constant to allow for type checking) called MoreStuff. We'll give this collection what it needs somewhere else, for now it is simply waiting for imports and when those imports are satisfied, we merge them into our master ListOfMoreStuff collection.

We also reference IService. If you remember, when we set that up back in part 1, we went ahead and added an Export tag. Here, we import it. Notice that on the setter, when the import happens, I go ahead and call the GetMoreStuff method, which returns me the Spanish numbers. When these are loaded, we merge them into our master ListOfMoreStuff. So the list will contain the results of the service call and any MoreStuff imports we may find.

We also want to dynamically display another view, but we don't know what that view is yet. Remember in part 2 I explicitly set the export value of IRegionManager using Unity to resolve it? This is where we will import it!

The DynamicViewCommand gives us a command to bind to in order to show that view. The view gets imported with another magic string (again, just stating I know we don't like them, but they are there for the sake of brevity in this example only) called DynamicView. In the constructor for the view model, we bind the command to a call to RegisterViewWithRegion and pass in the dynamic view.

This demonstrates how powerful MEF really is: we are able to specify an action to dynamically show a view here in the view model without any prior knowledge of where the view is or even what it is composed of!

The rest of the view model code simply initializes lists, combines them, etc. We've got our view model in place. How about some views that use it?

The MEF Views

In the views folder, I created two views. The first or "main" view is simply called MoreStuffView and it binds to the ListOfMoreStuff collection as well as the dynamic view command:


<ListBox ItemsSource="{Binding ListOfMoreStuff}">
   <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding}"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>
<Button Grid.Column="1" cal:Click.Command="{Binding DynamicViewCommand}" Content="Add View"/>

The code behind requires two pieces: first, we need to import the view model and bind it:


[Import]
public MoreStuffViewModel ViewModel
{
   get { return (MoreStuffViewModel)LayoutRoot.DataContext; }
   set { LayoutRoot.DataContext = value; }
}

Next, we need to call our friend, the PartInitializer class, to satisfy all of the imports in the constructor:


public MoreStuffView()
{            
    InitializeComponent();           
    PartInitializer.SatisfyImports(this);                
}

And that's it ... all of the services, exports, etc will be wired in elsewhere. I created a second view called DynamicView. This view simply contains a text block that indicates it was dynamically loaded. Remember how we had our "magic string" import for a view in the view model? Here, we will export the dynamic view in the code behind. There is only one change to the auto-generated code behind file, and that is to add the Export tag:


[Export("DynamicView")]
public partial class DynamicView : UserControl
{
    public DynamicView()
    {
        InitializeComponent();
    }
}

That's it for the view model and the views!

Providing Exports

The exports can obviously be supplied in a variety of ways. For this example, I simply created a class and exported a few values to demonstrate that the imports were working and merging with the main list:


public class MoreStuffExports
{        
    [Export("MoreStuff")]
    public string Item1
    {
        get { return "MEF Export 1"; }
    }

    [Export("MoreStuff")]
    public string Item2
    {
        get { return "MEF Export 2"; }
    }            
}

The MEF Module Initializer

Now it's time to wire everything together. There is actually almost nothing to the module initializer class. Because we base it on the PartModule we defined in the last post, the MEF-specific actions happen as part of the base class. In this class, we simply register the main view:


public class MEFInit : PartModule
{
    IRegionManager _regionManager;

    public MEFInit(IRegionManager regionManager)
    {
        _regionManager = regionManager;
    }

    #region IModule Members

    public override void Initialize()
    {
        base.Initialize(); // gotta call base for the MEF magic to happen
        _regionManager.RegisterViewWithRegion("MainRegion", typeof(MoreStuffView));
    }

    #endregion
}

That's it!

Conclusion

The goal of this this small series is to demonstrate how well PRISM and MEF work together, and how accessible modular and extensible applications can be in Silverlight 3. You've learned several ways to marry the view model to the view using different types of dependency injection containers and patterns. You've also seen how to take advantage of dynamically loaded modules to create application extensions that have an extremely small footprint but can easily integrate with the flexibility provided by both Unity and MEF in the context of PRISM.

Download the Source Code for this example

Jeremy Likness