Series recap:
- PRISM, MEF, and MVVM Part 1 of 3: Unity Glue
- PRISM, MEF, and MVVM Part 2 of 3: Making PRISM MEF Friendly
- PRISM, MEF, and MVVM Part 3 of 3: Dynamic MEF Modules in PRISM
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.