In the past I’ve made some screencast videos around PRISM or “Composite Application Guidance” for Silverlight.
I’ve also made some screencast videos around the Managed Extensibility Framework in Silverlight.
Not so long ago, the PRISM folks announced that they would be integrating MEF into PRISM with the forthcoming V4 and so I thought it would be good to take a look at what’s going on with PRISM 4 by taking the latest drop from CodePlex ( the August drop ).
I also noticed that John had recently done a Silverlight TV video on PRISM as well which you can find here.
As a recap – what’s PRISM? It’s some framework code and guidance ( in the form of documentation and reference applications ) for building “large-scale” or “composite” Silverlight (or WPF) applications.
What does “composite” mean here?
My own view is that it’s an application made up of a set of pieces where those pieces are designed to slot together without being too tightly coupled in order that they can potentially be produced by different teams and in order that they lend themselves to testing and maintenance.
Those “pieces” might be UI pieces such as “the stock chart view” or perhaps or, equally, they might be non-UI “services” which provide data to the UI like the “stock chart data provider”.
For me, a canonical example of that kind of application might be Visual Studio – it’s put together by lots of different teams and I doubt that (for instance) the people that wrote the Workflow designer for Visual Studio 2010 had too much of a dependency on the people that wrote the Entity Framework designer but they both end up in the same application and their respective pieces might even go through revisions without necessarily revising Visual Studio itself.
Not every Silverlight application needs PRISM. I don’t think anyone needs to feel PRISM-related-guilt because they’re building an application where they feel that PRISM would be overkill or where they feel that they have other solutions to the problems that PRISM attempts to solve.
The other thing I’d say about PRISM is that it’s not an all-or-nothing approach. The framework has lots of different pieces and you can use some of them without necessarily having to bring in all of them.
At the very heart of PRISM is the use of a Container that implements IServiceLocator which allows one bit of componentry to look up another without having to know too much about where it came from and opens the door to dependency injection as the Container creates and returns an object graph to the component making the request.
PRISM uses the Unity container as its default but now with the forthcoming version 4 it will also bring in MEF.
Over and above this, PRISM brings concepts such as;
- Modules
- Catalogs
- Shells containing Regions into which you can inject Views
- Loosely coupled eventing
and it’s “interesting” because MEF also has the notion of Catalogs and so you start to wonder where the overlaps and joins might be as these two things come together.
I figured I’d experiment a little to see if I could work some things out. Let’s imagine that I want to create the simplest application in the world. So simple I can draw it in PowerPoint;
Whilst this is ridiculously simple, it still requires a whole bunch of setup to use PRISM to do it so, for me, it’s not pointless and I actually find working through something so simple quite helpful because I feel that to start with more complex examples whilst also trying to figure out PRISM is just way too hard.
The Unity Route
Firstly, I tried to put something together based on Unity. Here’s my “app” running with some annotations on it;
I made a very deliberate choice that when the Button up there is clicked, it fires a Command on the ViewModel and that Command calls into the underlying Model which stores the current colour for the application.
I made that Model single-instanced. However, the Model does not implement property changed notification or any regular mechanism to notify ViewModels that the colour has changed. Instead it uses PRISM’s event aggregation system to publish the colour change.
I did that purely so that I could include event aggregation.
Where this ended up is in a top-level project which contained my App which runs a bit of code at start-up;
private void Application_Startup(object sender, StartupEventArgs e) { MyUnityBootstrapper bootstrapper = new MyUnityBootstrapper(); bootstrapper.Run(); }
and that’s making use of my custom MyUnityBootstrapper;
using System; using System.Windows; using Microsoft.Practices.Composite.Modularity; using Microsoft.Practices.Composite.Regions; using Microsoft.Practices.Composite.UnityExtensions; namespace UnityApplication { public class MyUnityBootstrapper : UnityBootstrapper { protected override IModuleCatalog CreateModuleCatalog() { return (Microsoft.Practices.Composite.Modularity.ModuleCatalog.CreateFromXaml( new Uri("catalog.xaml", UriKind.Relative))); } protected override DependencyObject CreateShell() { Shell shell = new Shell(); Application.Current.RootVisual = shell; return (shell); } protected override void InitializeShell() { base.InitializeShell(); IRegionManager manager = this.Container.TryResolve<IRegionManager>(); manager.RegisterViewWithRegion("MainRegion", typeof(MainView)); } } }
what’s this doing? Well, one thing it’s doing is creating my Shell and setting that as the RootVisual for the application. The Shell looks like this;
<UserControl x:Class="UnityApplication.Shell" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:rgn="clr-namespace:Microsoft.Practices.Composite.Presentation.Regions;assembly=Microsoft.Practices.Composite.Presentation" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"> <Grid x:Name="LayoutRoot"> <ContentControl VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" rgn:RegionManager.RegionName="MainRegion" /> </Grid> </UserControl>
It’s clear to see that the Shell is just hosting a single region called MainRegion. When we hit InitializeShell() up above, we grab the PRISM IRegionManager and we tell it that we want to inject the MainView into the MainRegion that we’ve marked out in the XAML.
I dropped the MainView that I defined into the same top-level application project for convenience and it’s just a couple of sub-regions as below;
<UserControl x:Class="UnityApplication.MainView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:rgn="clr-namespace:Microsoft.Practices.Composite.Presentation.Regions;assembly=Microsoft.Practices.Composite.Presentation" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"> <Grid x:Name="LayoutRoot"> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <ContentControl rgn:RegionManager.RegionName="RegionA" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" /> <ContentControl rgn:RegionManager.RegionName="RegionB" Grid.Column="1" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" /> </Grid> </UserControl>
so you can see that the MainView does nothing but host 2 more regions – RegionA and RegionB.
The one other thing that’s happening in my MyUnityBootstrapper is that I’m telling PRISM where to find things by creating a catalog and, specifically, I’m doing that from a XAML file Catalog.xaml which is in my application project as a Content file. It looks like this;
<?xml version="1.0" encoding="utf-8" ?> <Modularity:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:Modularity="clr-namespace:Microsoft.Practices.Composite.Modularity;assembly=Microsoft.Practices.Composite"> <Modularity:ModuleInfo Ref="Model.xap" ModuleName="ModelModule" ModuleType="Model.Modules.Module, Model, Version=1.0.0.0"> </Modularity:ModuleInfo> <Modularity:ModuleInfo Ref="RegionA.xap" ModuleName="RegionA" ModuleType="RegionA.Modules.RegionAModule, RegionA, Version=1.0.0.0"> <Modularity:ModuleInfo.DependsOn> <sys:String>ModelModule</sys:String> </Modularity:ModuleInfo.DependsOn> </Modularity:ModuleInfo> <Modularity:ModuleInfo Ref="RegionB.xap" ModuleName="RegionB" ModuleType="RegionB.Modules.RegionBModule, RegionB, Version=1.0.0.0"> <Modularity:ModuleInfo.DependsOn> <sys:String>ModelModule</sys:String> </Modularity:ModuleInfo.DependsOn> </Modularity:ModuleInfo> </Modularity:ModuleCatalog>
This is pretty simple and if we could author it in English we’d say;
This app has 3 more modules and they are contained in Model.xap,RegionA.xap, RegionB.xap and the only other thing to know is that both RegionA and RegionB depend on Model.
We might have to wait a little for ModuleCatalog.CreateFromEnglish() to show up so, in the meantime, XAML is just fine
So, what’s in these other Modules? I have my supposed “Model" which is the repository of all the data in my application which is just a single Colour value. I abstracted this comprehensive Model behind an interface;
namespace InterfacesLibrary { public interface IColourModel { Color CurrentColour { get; } void ChangeColour(Color newColour); } }
and put that interface into a separate InterfacesLibrary.dll assembly which can then be referenced from anywhere that needs it and one place that definitely needs it is the Model implementation itself;
using System.Windows.Media; using EventLibrary; using InterfacesLibrary; using Microsoft.Practices.Composite.Events; namespace Model { /// <summary> /// No INPC here. /// </summary> public class ColourModel : IColourModel { public ColourModel(IEventAggregator eventAggregator) { _currentColour = Colors.Red; _eventAggregator = eventAggregator; } public Color CurrentColour { get { return (_currentColour); } } /// <summary> /// a deliberately unfriendly method call required to change the /// colour. /// </summary> public void ChangeColour(Color newColour) { this._currentColour = newColour; var changeEvent = _eventAggregator.GetEvent<ColourChangeBroadcastEvent>(); changeEvent.Publish(newColour); } Color _currentColour; IEventAggregator _eventAggregator; } }
this is pretty simple and only unusual in that it offers a read-only CurrentColour property but requires someone to call the ChangeColour method to change the value. It’s also unusual in that it uses the PRISM eventing system to send out a change notification around the change of colour.
That ColourChangeBroadcastEvent is a class I built into a separate EventLibrary.dll assembly so that it can be referenced by anything that depends on it and it looks like;
using System.Windows.Media; using Microsoft.Practices.Composite.Presentation.Events; namespace EventLibrary { public class ColourChangeBroadcastEvent : CompositePresentationEvent<Color> { } }
My ColourModel is built into its own XAP called Model.xap and the other thing that resides in that XAP is an implementation of PRISM’s IModule interface which is necessary to provide a well known initialisation point for the module. It looks like this;
using InterfacesLibrary; using Microsoft.Practices.Unity; using UtilityLibrary; namespace Model.Modules { public class Module : BaseModule { public Module(IUnityContainer container) : base(container) { } protected override void RegisterTypes() { base.Container.RegisterType<IColourModel, ColourModel>( new ContainerControlledLifetimeManager()); } } }
where my BaseModule is nothing special;
using Microsoft.Practices.Composite.Modularity; using Microsoft.Practices.Unity; namespace UtilityLibrary { public abstract class BaseModule : IModule { public BaseModule(IUnityContainer container) { this.Container = container; } public void Initialize() { this.RegisterTypes(); } protected abstract void RegisterTypes(); protected IUnityContainer Container { get; set; } } }
and the Module itself simply registers my ColourModel as (effectively) a singleton implementation of IColourModel for the application so that anyone brave enough to ask for it will always get the same instance.
So, that’s the Model.xap package. I have 2 more packages in my Catalog.xaml which depend on that Model.xap which are RegionA.xap and RegionB.xap.
Both of these are pretty similar in that they each contain one View one ViewModel and one Module structured like RegionA is below;
and the view in this case is very simple;
<UserControl x:Class="RegionA.Views.RegionAView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"> <Grid x:Name="LayoutRoot" Background="{Binding CurrentColour}"> <Viewbox Margin="24"> <Button Content="Change Colour To Yellow" Command="{Binding ChangeColourCommand}" /> </Viewbox> </Grid> </UserControl>
where we are displaying the CurrentColour and we are also offering to run the ChangeColourCommand. There’s a tiny bit of code-behind living behind this in order to get the ViewModel injected (I’m not suggesting this is the right way to do this BTW);
using System.Windows.Controls; using Microsoft.Practices.Unity; using RegionA.ViewModels; namespace RegionA.Views { public partial class RegionAView : UserControl { public RegionAView() { InitializeComponent(); } [Dependency] public RegionAViewModel ViewModel { set { this.DataContext = value; } } } }
and then there’s the RegionAViewModel itself;
using System; using System.Windows.Input; using System.Windows.Media; using InterfacesLibrary; using Microsoft.Practices.Composite.Presentation.Commands; using UtilityLibrary; namespace RegionA.ViewModels { public class RegionAViewModel : PropertyChangeNotification { public RegionAViewModel(IColourModel colourModel) { _colourModel = colourModel; _changeColourCommand = new Lazy<DelegateCommand<object>>( () => new DelegateCommand<object>(OnChangeColour)); } public ICommand ChangeColourCommand { get { return (_changeColourCommand.Value); } } public SolidColorBrush CurrentColour { get { // not caching this brush return (new SolidColorBrush(_colourModel.CurrentColour)); } } void OnChangeColour(object parameter) { _colourModel.ChangeColour(Colors.Yellow); RaisePropertyChanged("CurrentColour"); } Lazy<DelegateCommand<object>> _changeColourCommand; IColourModel _colourModel; } }
which offers the ChangeColourCommand and always delegates everything down to the underlying IColourModel which will get injected at construction time.
That’s all simple enough, the remaining piece is to have this module provide some initialisation which sets all these things up for Unity/PRISM and I do that in my Module;
using Microsoft.Practices.Composite.Regions; using Microsoft.Practices.Unity; using RegionA.ViewModels; using RegionA.Views; using UtilityLibrary; namespace RegionA.Modules { public class RegionAModule : BaseModule { public RegionAModule(IUnityContainer container) : base(container) { } protected override void RegisterTypes() { base.Container.RegisterType<RegionAView>(); base.Container.RegisterType<RegionAViewModel>(); IRegionManager regionManager = base.Container.Resolve<IRegionManager>(); regionManager.RegisterViewWithRegion("RegionA", typeof(RegionAView)); } } }
BTW – there’s more than one hard-coded string flying around here which, obviously, aren’t-so-realistic.
Anyway, this registers the RegionAView, RegionAViewModel and then grabs the PRISM IRegionManager and tells it that the view for the region called RegionA is represented by the RegionAView.
The RegionB.xap package is almost identical to the RegionA one except its view is slightly different as it only displays the current colour;
<UserControl x:Class="RegionB.Views.RegionBView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"> <Grid x:Name="LayoutRoot" Background="White"> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="3*"/> </Grid.RowDefinitions> <Viewbox> <TextBlock Text="Current Colour" /> </Viewbox> <Rectangle Grid.Row="1" Fill="{Binding CurrentColour}" /> </Grid> </UserControl>
and the underlying ViewModel;
using System.Windows.Media; using EventLibrary; using InterfacesLibrary; using Microsoft.Practices.Composite.Events; using UtilityLibrary; namespace RegionB.ViewModels { public class RegionBViewModel : PropertyChangeNotification { public RegionBViewModel(IColourModel colourModel, IEventAggregator eventAggregator) { _colourModel = colourModel; var changeEvent = eventAggregator.GetEvent<ColourChangeBroadcastEvent>(); if (changeEvent != null) { changeEvent.Subscribe(OnColourChange); } } public SolidColorBrush CurrentColour { get { return (new SolidColorBrush(_colourModel.CurrentColour)); } } /// <summary> /// don't want this to be public but I think the event system dictates /// that it has to be /// </summary> public void OnColourChange(Color newColour) { base.RaisePropertyChanged("CurrentColour"); } IColourModel _colourModel; } }
syncs itself up to the ColourChangeBroadcastEvent fired by PRISMs eventing system in order to update whenever the colour changes down on the underlying model.
That’s it – all fairly boilerplate but, as I said at the start, quite a lot of moving parts in place just to get something really simple up and running but having got to that point adding new parts is not so difficult and nor is versioning/maintaining existing ones.
The MEF Route
How does this change if we use MEF rather than use Unity?
Before even looking at what PRISM/MEF do together I looked at the code that I’d written above and thought about what I saw and what my expectations would be if I used MEF instead of Unity.
My random thoughts;
- The code I’ve written so far has quite a lot of “handshaking” and quite a lot of that is between my code and Unity – that is, quite a lot of it has nothing to do with PRISM at all but is instead, related to getting things set up in a Unity container. I would hope that this would go away with MEF as MEF likes to “discover” rather than “be told”.
- I would also hope that I could export a view into a region with MEF by using some kind of ExportViewToRegion attribute or similar. That would be my expectation.
- I would still expect to use a PRISM catalog in order to use MEF because otherwise I don’t see how MEF will be able to find my XAP files. If that’s not the case then I’d expect to have to do some manual work of my own with DeploymentCatalog but I’m hoping that I won’t have to do that.
- Whilst I don’t see it as essential for MEF to need to have the various package dependencies described to it as I did with my RegionA, RegionB and Model packages for Unity I can also see that it still makes a tonne of sense to load Model before we load RegionA or RegionB so I’d expect to still have to document dependencies to get a sensible load order. That would make double sense if we were trying to defer loading on certain packages or sets of packages.
So, let’s see how this works out…
The first thing I needed to do was replace my class MyUnityBootstrapper with a version for MEF;
public class MyMEFBootstrapper : MefBootstrapper { protected override IModuleCatalog CreateModuleCatalog() { return (Microsoft.Practices.Composite.Modularity.ModuleCatalog.CreateFromXaml( new Uri("catalog.xaml", UriKind.Relative))); } protected override void ConfigureAggregateCatalog() { base.ConfigureAggregateCatalog(); this.AggregateCatalog.Catalogs.Add( new AssemblyCatalog(Assembly.GetExecutingAssembly())); } protected override DependencyObject CreateShell() { Shell shell = new Shell(); Application.Current.RootVisual = shell; return (shell); } protected override void InitializeShell() { base.InitializeShell(); IRegionManager regionManager = this.Container.GetExportedValue<IRegionManager>(); regionManager.RegisterViewWithRegion("MainRegion", typeof(MainView)); } }
and then I changed my application initialisation code in order that it creates an instance of MyMefBootstrapper rather than the MyUnityBootstrapper;
private void Application_Startup(object sender, StartupEventArgs e) { MyMEFBootstrapper bootstrapper = new MyMEFBootstrapper(); bootstrapper.Run(); }
There’s 3 things of note here.
- I’m still using the exact same catalog.xaml file to define my modules – previously I had 3 modules that needed loading and I still have 3 modules that need loading so the scenario doesn’t feel any different to me and so I’m pleased that my config file is still valid having switched to MEF.
- I override ConfigureAggregateCatalog. The base MefBootstrapper creates an AggregateCatalog for me and all I’m doing here is adding an AssemblyCatalog for my main application assembly because that assembly contains my MainView so I need it to be part of my MEF catalogs.
- In InitializeShell() ( which is possibly not the right place ) I manually grab the IRegionManager and register my MainView type as the view for the region called “MainRegion”.
Clearly, because I can get hold of the AggregateCatalog that the base MefBootstrapper has created for me I could then do anything I want with MEF catalogs and add them to the aggregate but, here, I’m allowing the PRISM ModuleCatalog to continue to do that work for me.
The only thing here that doesn’t feel quite “right” to me is having to manually tell the RegionManager about my view. I was hoping that if I exported my view in the right way, PRISM would just “find it” and do the right thing with it.
If I take a look at my Model then I’ve changed the code there to make sure that the implementation exports itself via MEF and also imports PRISM’s IEventAggregator interface which it needs;
[Export(typeof(IColourModel))] [PartCreationPolicy(CreationPolicy.Shared)] public class ColourModel : IColourModel { [ImportingConstructor] public ColourModel(IEventAggregator eventAggregator) { _currentColour = Colors.Red; _eventAggregator = eventAggregator; } public Color CurrentColour { get { return (_currentColour); } } /// <summary> /// a deliberately unfriendly method call required to change the /// colour. /// </summary> public void ChangeColour(Color newColour) { this._currentColour = newColour; var changeEvent = _eventAggregator.GetEvent<ColourChangeBroadcastEvent>(); changeEvent.Publish(newColour); } Color _currentColour; IEventAggregator _eventAggregator; }
and then I reworked the implementation of IModule that lives in the assembly with that ColourModel class;
[ModuleExport(typeof(Module))] public class Module : IModule { public Module() { } public void Initialize() { } }
now I must admit that this feels a little odd to me. It’s nice to not have to register this implementation explicitly with a container ( thanks MEF! ) but this class now serves no purpose for me and yet I think that if I want to use the ModuleCatalog in the way that I’m using it then I have to have an implementation of IModule in the XAP package that I point to from my configuration file. Here’s the corresponding config file entry;
<Modularity:ModuleInfo Ref="Model.xap" ModuleName="ModelModule" ModuleType="Model.Modules.Module, Model, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"> </Modularity:ModuleInfo>
and I don’t think there’s any way to get rid of the ModuleType attribute even though my Model.Modules.Module class doesn’t actually do anything in its implementation.
This led me to thinking that maybe I forget about configuring model.xap via the config file and, instead, using a MEF DeploymentCatalog manually myself and loading it up that way but the simplicity of using the config file won out for the moment even if it means I have this class that doesn’t really do anything. But I’ll return to that idea…
When it comes to my 2 XAP packages that contain Views and ViewModels they both largely look the same. Here’s my RegionAModule that lives in the XAP package with my RegionAView and RegionAViewModel;
[ModuleExport(typeof(RegionAModule))] public class RegionAModule : IModule { [ImportingConstructor] public RegionAModule(IRegionManager regionManager) { _regionManager = regionManager; } public void Initialize() { _regionManager.RegisterViewWithRegion("RegionA", typeof(RegionAView)); } IRegionManager _regionManager; }
Note that this is using the ModuleExport attribute which is a custom ExportAttribute provided by PRISM on top of MEF. Note also that this imports PRISM’s IRegionManager and manually registers the type RegionAView as the view for “RegionA”.
RegionAView looks like this ( the XAML for this control hasn’t changed so I won’t paste it again here );
[Export] [PartCreationPolicy(CreationPolicy.NonShared)] public partial class RegionAView : UserControl { public RegionAView() { InitializeComponent(); } [Import] public RegionAViewModel ViewModel { set { this.DataContext = value; } } }
so this view imports its ViewModel ( not necessarily in the best way because it’s a bit tricky to replace this ViewModel implementation for one thing ) and it also Exports itself to MEF.
I find that export a little weird – there’s no code importing this view and so it feels artificial to me to have to export it but I’ve yet to find a way around it because PRISM is going to go to the “MEF Container” asking for one of these views and so it has to be exported to get it into that container and I don’t want to manually add it to the Container.
The corresponding RegionAViewModel exports itself and imports the underlying IColourModel to get to its model;
[Export] [PartCreationPolicy(CreationPolicy.NonShared)] public class RegionAViewModel : PropertyChangeNotification { [ImportingConstructor] public RegionAViewModel(IColourModel colourModel) { _colourModel = colourModel; _changeColourCommand = new Lazy<DelegateCommand<object>>( () => new DelegateCommand<object>(OnChangeColour)); } public ICommand ChangeColourCommand { get { return (_changeColourCommand.Value); } } public SolidColorBrush CurrentColour { get { // not caching this brush return (new SolidColorBrush(_colourModel.CurrentColour)); } } void OnChangeColour(object parameter) { _colourModel.ChangeColour(Colors.Yellow); RaisePropertyChanged("CurrentColour"); } Lazy<DelegateCommand<object>> _changeColourCommand; IColourModel _colourModel; }
and there’s nothing at all unusual about the imports/exports here as I need to export my RegionAViewModel ( ok, I probably should have introduced an interface here ) and I need to import my IColourModel so that’s all good.
My RegionB XAP module along with its IModule implementation, its RegionBView and RegionBViewModel are all largely the same with the exception of my RegionBViewModel which uses the IEventAggregator from PRISM and so has changed a little;
[Export] [PartCreationPolicy(CreationPolicy.NonShared)] public class RegionBViewModel : PropertyChangeNotification { [ImportingConstructor] public RegionBViewModel(IColourModel colourModel, IEventAggregator eventAggregator) { _colourModel = colourModel; var changeEvent = eventAggregator.GetEvent<ColourChangeBroadcastEvent>(); if (changeEvent != null) { changeEvent.Subscribe(OnColourChange); } } public SolidColorBrush CurrentColour { get { return (new SolidColorBrush(_colourModel.CurrentColour)); } } /// <summary> /// don't want this to be public but I think the event system dictates /// that it has to be /// </summary> public void OnColourChange(Color newColour) { base.RaisePropertyChanged("CurrentColour"); } IColourModel _colourModel; }
but it’s not particularly radical – I just made my constructor an ImportingConstructor to make sure that implementations of IColourModel and IEventAggregator were provided for me.
At that point – this code seems to work the same way as my original code worked with Unity but there’s a couple of things that feel a little weird in a “MEF world”;
- Having to write implementations of IModule which don’t do anything but are needed so that I can refer to them from my PRISM catalog configuration.
- Having to manually get hold of the IRegionManager and manually register my views against their regions.
- Having to export my views (RegionAView, RegionBView) even though I know that nothing actually imports them.
If I had to categorise where I’m up to I’d say that in this early drop it feels like PRISM is using MEF as an IoC Container much like it does with Unity but it doesn’t feel fully MEFfed to me
I thought I’d experiment with seeing how far I could go towards “experimenting” around those 3 points above and I split that experimentation into the following 2 sub sections – A and B.
A – Who’s Importing My Views?
The way that my code stands at the moment, there’s nothing that’s actually importing my views – I have 3 of them and they end up on the screen because I have manual code that uses an IRegionManager to manually call RegisterViewWithRegion() in from a couple of places;
- From my MefBootstrapper – I use this to register my MainView into its MainRegion because this view is built into my main XAP so it’s ready to go at application startup.
- From my 2 separate XAP modules – RegionA.XAP and RegionB.XAP – each of these uses its implementation of IModule.Initialize() in order to call into the RegionManager again.
I wondered – what if I took all that manual code away and then had MEF compose a class for me which imported my views in a recomposable way and also imported the PRISM region manager or region registry and, as new views show up, that class can register them with the registry.
It seemed reasonable but I’m not sure that I got there just yet.
I removed all the code I had that interacted with the IRegionManager directly.
Then I went and wrote a little custom export attribute for MEF;
[MetadataAttribute] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property)] public class ExportViewAttribute : ExportAttribute { public ExportViewAttribute() : base(typeof(UserControl)) { } public ExportViewAttribute(string contractName) : base(contractName, typeof(UserControl)) { } public string RegionName { get; set; } } public interface IExportViewMetadata { string RegionName { get; } }
Now, I’m not at all sure I have that correct yet but the idea is to be able to export a View into a particular Region defined by the RegionName ( once again, relying on strings here ).
Then I re-worked both of my Region[A/B]ViewModel classes to make them a little less “brittle” in the sense that they had previously used ImportingConstructor whereas I felt it was more reasonable to make them import onto properties and allow for default values and re-composability as in ( only listing one of them here );
[Export] [PartCreationPolicy(CreationPolicy.NonShared)] public class RegionAViewModel : PropertyChangeNotification { public RegionAViewModel() { _changeColourCommand = new Lazy<DelegateCommand<object>>( () => new DelegateCommand<object>(OnChangeColour)); } [Import(AllowDefault=true,AllowRecomposition=true)] public IColourModel ColourModel { get; set; } public ICommand ChangeColourCommand { get { return (_changeColourCommand.Value); } } public SolidColorBrush CurrentColour { get { // not caching this brush return (new SolidColorBrush( ColourModel == null ? Colors.Red : ColourModel.CurrentColour)); } } void OnChangeColour(object parameter) { if (ColourModel != null) { ColourModel.ChangeColour(Colors.Yellow); RaisePropertyChanged("CurrentColour"); } } Lazy<DelegateCommand<object>> _changeColourCommand; }
so this is a little more resilient to the implementation of IColourModel showing up in a MEF catalog after the RegionAViewModel is already instantiated. I did a very similar thing to my RegionBViewModel.
With that in place, I set about trying to write a class which would import these exported views. Here’s my first attempt;
public class MyRegionWatcher { [Import] public IRegionViewRegistry RegionRegistry { get; set; } [ImportMany(AllowRecomposition = true)] public ObservableCollection<ExportFactory<UserControl, IExportViewMetadata>> Views { get { return (_views); } set { if ((_views != value) && (_views != null)) { _views.CollectionChanged -= OnViewsChanged; } _views = value; if (_views != null) { _views.CollectionChanged += OnViewsChanged; } } } void OnViewsChanged(object sender, NotifyCollectionChangedEventArgs e) { // HACK: only trying to handle add, not remove, reset, etc. if (e.Action == NotifyCollectionChangedAction.Add) { foreach (var item in e.NewItems) { ExportFactory<UserControl, IExportViewMetadata> view = (ExportFactory<UserControl, IExportViewMetadata>)item; // HACK: not sure creating one here is the right thing to do... // and I suspect I leak here too... RegionRegistry.RegisterViewWithRegion( view.Metadata.RegionName, () => view.CreateExport().Value); } } } ObservableCollection<ExportFactory<UserControl, IExportViewMetadata>> _views; }
and I dropped this class into my main XAP for now and overrode MefBootstrapper.ConfigureContainer in order to compose an instance of this class into my container;
public class MyMEFBootstrapper : MefBootstrapper { protected override void ConfigureContainer() { base.ConfigureContainer(); this.Container.ComposeParts(new MyRegionWatcher()); }
and that seemed to “work” in the sense that my UI showed up on the screen and so on but I suspect there’s a bunch of subtlety that I’m not getting right in there and that I’m probably being lucky in the order in which my modules are being loaded but it was an interesting “experiment” for a little while and I did manage to get away from having all that view registration in my separate modules.
B – Who’s Importing My Modules?
If I forget all the stuff I just did in section A and revert back to my code as it was before I introduced that experiment with MyRegionWatcher and all that stuff then I had 3 modules and 2 of them manually ran code inside of IModule.Initialize() in order to register view types with regions. These modules are loaded because they are described in my configuration file catalog.xaml and I load that up via PRISM’s ModuleCatalog class.
All was good if a bit manual.
But what if I wanted to mix in loading up some of my modules in a different way? What if 2 came from the catalog.xaml via PRISM’s ModuleCatalog but another was loaded via DeploymentCatalog with MEF.
Or, to put it another way, if I changed my catalog.xaml file to be;
<?xml version="1.0" encoding="utf-8" ?> <Modularity:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:Modularity="clr-namespace:Microsoft.Practices.Composite.Modularity;assembly=Microsoft.Practices.Composite"> <Modularity:ModuleInfo Ref="Model.xap" ModuleName="ModelModule" ModuleType="Model.Modules.Module, Model, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"> </Modularity:ModuleInfo> <Modularity:ModuleInfo Ref="RegionA.xap" ModuleName="RegionA" ModuleType="RegionA.Modules.RegionAModule, RegionA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"> <Modularity:ModuleInfo.DependsOn> <sys:String>ModelModule</sys:String> </Modularity:ModuleInfo.DependsOn> </Modularity:ModuleInfo> </Modularity:ModuleCatalog>
and then altered my MyMefBootstrapper class so that it attempted to use a DeploymentCatalog to load up the now missing RegionB.xap;
public class MyMEFBootstrapper : MefBootstrapper { protected override IModuleCatalog CreateModuleCatalog() { return (Microsoft.Practices.Composite.Modularity.ModuleCatalog.CreateFromXaml( new Uri("catalog.xaml", UriKind.Relative))); } protected override void ConfigureAggregateCatalog() { base.ConfigureAggregateCatalog(); base.AggregateCatalog.Catalogs.Add( new AssemblyCatalog(Assembly.GetExecutingAssembly())); DeploymentCatalog deploymentCatalog = new DeploymentCatalog(new Uri("RegionB.xap", UriKind.Relative)); base.AggregateCatalog.Catalogs.Add(deploymentCatalog); deploymentCatalog.DownloadAsync(); } protected override DependencyObject CreateShell() { Shell shell = new Shell(); Application.Current.RootVisual = shell; return (shell); } protected override void InitializeShell() { base.InitializeShell(); IRegionManager regionManager = this.Container.GetExportedValue<IRegionManager>(); regionManager.RegisterViewWithRegion("MainRegion", typeof(MainView)); } }
Then, firstly I think I’m creating a race condition here because if Model.xap loads before RegionB.xap then I’ll get away with things but if the reverse happens then I’ll hit a problem because my RegionBViewModel depends on the implementation of IColourModel that lives in Model.xap. This was all fine when I used ModuleCatalog for everything because the dependencies were described in the config file.
So, I should probably make my RegionBViewModel a little more resilient to that idea that its IColourModel implementation might show up “later”;
[Export] [PartCreationPolicy(CreationPolicy.NonShared)] public class RegionBViewModel : PropertyChangeNotification { [ImportingConstructor] public RegionBViewModel(IEventAggregator eventAggregator) { var changeEvent = eventAggregator.GetEvent<ColourChangeBroadcastEvent>(); if (changeEvent != null) { changeEvent.Subscribe(OnColourChange); } } [Import(AllowDefault=true,AllowRecomposition=true)] public IColourModel ColourModel { get { return (_colourModel); } set { _colourModel = value; base.RaisePropertyChanged("CurrentColour"); } } public SolidColorBrush CurrentColour { get { return (new SolidColorBrush( ColourModel == null ? Colors.Green : ColourModel.CurrentColour)); } } /// <summary> /// don't want this to be public but I think the event system dictates /// that it has to be /// </summary> public void OnColourChange(Color newColour) { base.RaisePropertyChanged("CurrentColour"); } IColourModel _colourModel; }
Ok, so I’ve “hardened” it up a little in that it can now accept the IColourModel implementation showing up later but I hit another problem when I try and run the code I’ve written;
The composition remains unchanged. The changes were rejected because of the following error(s): The composition produced multiple composition errors, with 10 root causes. The root causes are provided below. Review the CompositionException.Errors property for more detailed information.
1) Change in exports prevented by non-recomposable import 'Microsoft.Practices.Composite.MefExtensions.Modularity.MefModuleManager.MefXapModuleTypeLoader (ContractName="Microsoft.Practices.Composite.MefExtensions.Modularity.MefXapModuleTypeLoader")' on part 'Microsoft.Practices.Composite.MefExtensions.Modularity.MefModuleManager'.
2) Change in exports prevented by non-recomposable import 'Microsoft.Practices.Composite.MefExtensions.Modularity.MefModuleManager..ctor (Parameter="moduleInitializer", ContractName="Microsoft.Practices.Composite.Modularity.IModuleInitializer")' on part 'Microsoft.Practices.Composite.MefExtensions.Modularity.MefModuleManager'.
etc ( there’s 10 of these )
I think what’s going on here is that my main application references PRISM libraries that have a bunch of exports and my module that’s being loaded (RegionB.xap) also references PRISM libraries that have a bunch of exports so the exports show up more than once and some code somewhere wasn’t expecting that to happen.
Hmm, so for now I hacked my way around this by making use of a FilteredCatalog ( there’s also one in the PRISM source ) with a very hacky filter in it which changed my ConfigureAggregateCatalog override to;
protected override void ConfigureAggregateCatalog() { base.ConfigureAggregateCatalog(); base.AggregateCatalog.Catalogs.Add( new AssemblyCatalog(Assembly.GetExecutingAssembly())); DeploymentCatalog deploymentCatalog = new DeploymentCatalog(new Uri("RegionB.xap", UriKind.Relative)); deploymentCatalog.DownloadCompleted += (catalog, args) => { if (args.Error == null) { // HACK: Relax, this is just a big hack... FilteredCatalog filter = new FilteredCatalog((DeploymentCatalog)catalog, partDef => partDef.ExportDefinitions.Where( exportDef => ( exportDef.ContractName.Contains("Microsoft") && !exportDef.ContractName.EndsWith("IModule") )).Count() == 0); base.AggregateCatalog.Catalogs.Add(filter); } }; deploymentCatalog.DownloadAsync(); }
check out lines 16 to 23 – that would definitely need a bit of “refinement” but the intent is to avoid loading a bunch of exports here which aren’t actually my exports.
When I do this though, I realise that of my 3 modules;
- Model.xap – this is being loaded by the ModuleCatalog and I see IModule.Initialize being called on my implementation here.
- RegionA.xap – this is being loaded by the ModuleCatalog and I see IModule.Initialize being called on my implementation here.
- RegionB.xap – this is being loaded by the MEF DeploymentCatalog and I see no call to IModule.Initialize.
so, because of the 3rd bullet point above – my RegionBView doesn’t get registered with the RegionManager and so doesn’t show up on the screen because my IModule.Initialize code in that module isn’t running.
I’d kind of assumed that something in PRISM would have done some kind of [ImportMany] on IModule with recomposability switched on in order to spot the arrival of new IModule implementations and Initialize() them and a quick flick through the source-code suggests that there’s a class doing something like that – there’s a MefModuleInitializer and that looks to do an ImportMany and allows for recomposition but as far as I can tell that class doesn’t do anything as and when new IModule implementations turn up.
So, perhaps it’s possible to replicate that kind of idea with a class such as;
public class MyModuleInitializer { [ImportMany(AllowRecomposition=true)] public ObservableCollection<IModule> Modules { get { return (_modules); } set { if (_modules != null) { _modules.CollectionChanged -= OnModulesChanged; } _modules = value; if (_modules != null) { _modules.CollectionChanged += OnModulesChanged; } } } void OnModulesChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Add) { foreach (var item in e.NewItems) { ((IModule)item).Initialize(); } } } ObservableCollection<IModule> _modules; }
which just waits for implementations of IModule to show up and then tries to call Initialize on them. The only “problem” there is that it means there’s a strong chance of calling IModule.Initialize more than once so that’s not perfect by any means and I had to rework my two relevant Initialize routines so that they tried to protect against this as in;
[ModuleExport(typeof(RegionAModule))] public class RegionAModule : IModule { [ImportingConstructor] public RegionAModule(IRegionViewRegistry regionRegistry) { _regionRegistry = regionRegistry; } public void Initialize() { if (_regionRegistry.GetContents("RegionA").Count() == 0) { _regionRegistry.RegisterViewWithRegion("RegionA", typeof(RegionAView)); } } IRegionViewRegistry _regionRegistry; }
Having done that, I’d need to compose an instance of this MyModuleInitializer into my MEF container;
public class MyMEFBootstrapper : MefBootstrapper { protected override void ConfigureContainer() { base.ConfigureContainer(); base.Container.ComposeParts(new MyModuleInitializer()); }
which I just did temporarily by overriding the MefBootstrapper.ConfigureContainer() method.
That got me back to “working” code with 2 modules coming from ModuleCatalog and the other being loaded straight by MEF’s DeploymentCatalog and so that looks in principle to all kind of hang together even in my hacked experiement.
Summing Up
All in all, I’m liking where all this is heading – it’s early days yet but MEF integration has clearly already gone quite a way into PRISM.
What I’d like to see more of ( and perhaps what will show up in the future ) is less places where I have to write code to;
- tell PRISM which types implement IModule
- tell PRISM which views slot into which regions
and more of the MEF “discovery” way of working where PRISM just imports all my IModules and somehow imports all of my View types and slots them together.
However, as I say – it’s early days yet so I’m sure that the PRISM guys have a lot more tricks up their sleeves before PRISM 4 comes out so it’ll be good to follow up on later drops…