ViewModel 1st Child Container PRISM Navigation





5.00/5 (17 votes)
Shows how to use PRISM navigation API in VM 1st with child container support
Introduction
I recently had the good fortune to be able to start a green field project (literally from scratch). The only criteria I had was that it must be done in WPF, as that is what the rest of the deployable UIs are using, and it's also what the rest of the developers at the company know how to use. So WPF fair enough I like that stuff. So if I am doing a new WPF application what sort of things would I look to do, and how?
Here is my must have list:
- Composable UI (either using
DataTemplate
s or some sort of view resolution technique) - Full IOC support for every aspect of the application
- Some form of modularity
- Extensibility
Mmmm that sounds an aweful lot like the composite guidance for WPF : PRISM. Yeah you caught me, this article will in fact be about PRISM, where I will be discussing some alternative approaches to working with PRISM around the first 2 points in the list above. One thing you should know right now, is that this article is quite a niche article in a lot of ways, as it is not a golden bullet that you could just apply everywhere, it does however work very well in th correct scenario. That said if you use PRISM / want to use PRISM this may be of use to you.
Just for completeness what I typically do on any new WPF project, is pull in bits and pieces from all over the place, which lately looks a bit like this:
- PRISM for its regions/IOC support/modularity (oh by the way when I say PRISM I mean PRISM 4 / 4.1)
- Cinch for some of my helper classes
- Rx for doing async coding / streams
- Stuff from a personal library containing all sorts of disparate stuff
The Demo App Overview
As I stated previously I will be concentrating on the following 2 areas of working with PRISM
- Composable UI (either using
DataTemplate
s or some sort of view resolution technique) - Full IOC support for every aspect of the application
The demo application on the surface of it is a simple demonstration of PRISMs
region functionality, specifically a TabControl
selector region adaptor (which comes with PRISM
out of the box)
The demo application looks like this, not very exciting, I give you that.
YOU CAN CLICK THE IMAGE FOR A BIGGER VERSION
In fact this looks very dull, but read on, we will get some value out of this article if it kills me.
View Model First With PRISM
When developing XAML based applications you are either in the ViewModel first
camp, or the View first camp. Apparently designers love working with View first
and VisualState
(s) rather than DataTemplate
s. Thing is
PRISM
sorta/kinda (out of the box) forces your hand towards a more View first approach
where you must either
- Add a View to a region
- Navigate to a View within a region
Now that may seem reasonable enough, BUT and its a big one, have any of you actually worked with a designer (in fact have any of you ever seen one of these mythical XAML designer people, do they exist, they seem rarer than an abominable snowman, like I say I have only ever met one) on a XAML project that really knew their XAML and also knew Expression Blend inside out.
I have (but only
once), and guess what........they DID NOT work with a View first
appraoch, but they did like VisualState
(s), which you can use
anywhere actually. You basically have to pick Triggers (Sliverlight doesn't
support these anyway) or VisualState
(s). I think given the choice
designers would always go for VisualState
(s) anyway.
They DID however work pretty much
exclusively in DataTemplate
s. Now I am not talking an average
designer, they were good and definately knew their onions, and it was a complex
XAML based product, with literally 500,000 lines of code. It was big.
This assured me of 2 things:
- You can go ViewModel first, which is what developers love and want to use
- You can still use
VisualState
(s) which is what designers love and want to use. In fact this has little to do with VM first or View first, I just wanted to assure people that just because you went for a VM first approach, you should still be able to keep your designer folk happy.
Kind of like having your cake and eating it.....Mmmmm....tell me more you say.
Thing is, in the opening gambit of this section I stated that "PRISM sorta/kinda (out of the box) forces your hand towards a more View first approach". This is sorta/kinda true, in that every bit of documentation you will likely find on using PRISM and its region support and its navigation API will talk about a View first approach.
However hidden away in the documentation, and within a single awesome line (literally 1 line, blink and you would miss it) within the PRISM book/docs is a mechanism that you can use to do ViewModel first navigation using PRISMs navigation API. Awesome.
It essentially looks like this when you register a "ViewModel" for navigation within the IOC container (I am using Unity here, but for MEF it is even easier as you can just use the ViewModels full name as the export contract value) :
Container.RegisterTypeForNavigation<MainContainerDummyViewModel>();
Which makes use of the following simple extension method that I have written, which helps to make the IOC registration process a little simpler and less cluttered.
public static void RegisterTypeForNavigation<T>(this IUnityContainer container)
{
container.RegisterType(typeof(Object), typeof(T), typeof(T).FullName);
}
When using the Unity container the registering against the
typeof(Object)
is a vital part of the
registration. If you do not do it exactly like this, you will likely
just get the ViewModel name shown as a string representation rather that
the actual ViewModel that you hope to apply a DataTemplate
to.Now that we have a mechanism for registering a ViewModel for navigtion within the Unity IOC container, we might show a ViewModel within a region like this:
private void NavigateToMainContainerViewModel(CreateMainContainerViewModelMessage message)
{
UriQuery parameters = new UriQuery();
parameters.Add("ID", mainContainerCounter++.ToString());
var uri = new Uri(typeof(MainContainerDummyViewModel).FullName + parameters, UriKind.RelativeOrAbsolute);
regionManager.RequestNavigate("MainRegion", uri, HandleNavigationCallback);
}
This actually works just fine. Like I say PRISM does support ViewModel fisrt, it is just not that well known or publicised. Thing is once you know it, its easy to do.
How Can We Structure Things A Bit Better
Ok so where are we, we now know that we can indeed actually support a ViewModel
first approach using PRISM,
which keeps us developers happy. And as I say when I was actually working with
one of these mythical designers that knows XAML/Blend (rather than PhotoShop)
they were just fine working with DataTemplates
and the thing they
like A.K.A the VisualState
(s).
Thing is if you can imagine a medium to large scale project with 100nds of ViewModel and possibly 1000nds of UI Services (at one place I worked they had this), it is not so hard to imagine that having a single application IOC container, and having to carefully think out all the component life cycles that are registered within the container would soon become:
- Fairly overwhelming : as you would have to really really understand how and when each component was to be used. Could you really know exactly when something that makes use of an IOC registered component will be used and released. This is kind of down to how the users will use the system I feel
- Poorly structured : due to there being only one container that shared all the component regisitrations (ok this would likely be done across serveral modules in PRISM, but the underlying issue is the same, there is only 1 container)
- Maintenance nightmare : for obvious reasons
- Would likely be fairly brittle : as you may not fully understand the proper life cycles of your IOC components, as they may be shared between ViewModels that come in and out of existence. What life time management would you use. Singleton? Shared?
My personal feeling is a lot of these issues can be solved by using child containers. Now PRISM ships with support for Unity and MEF, but some of the work by the community has allowed you to plug in other IOC containers into PRISM. For this article I am using Unity as it does the job well enough and I like its support for child containers. I have not really used the child container approach with MEF so can't comment on it really. Certainly the approach I am about to discuss here would work just fine with Castle Windsor say.
Now I am not saying that having a child container is the answer to every WPF issue you will come across, as I have stated already, it is very niche, I totally get that. The areas in which it would work very well are things like
- Some sort of Tabbed interface, where each tab could be a ViewModel and the Tab could be closeable
- Some sort of workspace interface where you may have small portions of the UI represented by closeable viewmodels
Essentially anywhere, where you will be on control of calling Dispose() on the ViewModel in response to some user action (such as a Tab close, or closing/removing a ViewModel) might benefit from using a child container.
A typical arrangement that I would go for when using PRISM in this way might be something like this:
So that is the general idea, let's now have a look at some code.
Make sure we can create a child container registration for use with PRISM. As before I have created a simple extension method to make this work as easy as possible
public static void RegisterTypeForNavigationWithChildContainer<T>(this IUnityContainer container)
{
container.RegisterType(typeof(Object), typeof(T), typeof(T).FullName,
new InjectionMethod("AddDisposable", new object[] { container }));
}
It can be seen this code is much the same as the previous Unity registration
code, there is however one new thing of note here. Which is that the child
container is being passed to an AddDisposable
method, lets have a
look at that. The idea being that when the ViewModel is disposed either
programatically or via GC the child container and all its registered components
would also be disposed. As I say it will not work for every thing, but for
closeable ViewModel (think Tabbed UI) / workspace type UI designs it works very
well.
NOTE : I am making use of Reactive Extensions here, but that is just beacuse
I decided to make my Event Aggregator using Rx, but the CompositeDisposable
which you see below could easily be swapped for a List<IDisposable>
should you not want to use Rx.
public abstract class DisposableViewModel : INPCBase, IDisposable
{
CompositeDisposable disposables = new CompositeDisposable();
public void AddDisposable(IDisposable disposable)
{
disposables.Add(disposable);
}
public void Dispose()
{
foreach (var disposable in disposables)
{
disposable.Dispose();
}
}
}
So let's now see how we deal with creating a new ViewModel and showing it using PRISM and also how to tie the lifetime of the child container to the ViewModel. As I say this will not suite everyone, it is a very niche requirement, but one that I personally have found extremely useful.
/// <summary>
/// Creates a child container for a viewmodel and registers its dependencies and then shows the ViewModel. It
/// also ties the lifetime of the child container to that of the newly instantatied ViewModel.
/// </summary>
/// <param name="message">The message that has been seen to create a new ViewModel to show</param>
private void NavigateToChildContainerViewModel(CreateChildContainerViewModelMessage message)
{
var childcontainer = mainAppContainer.CreateChildContainer();
//note if you want to have services that are disposable that you want disposed when child container is disposed
//they need to be registered using the "HierarchicalLifetimeManager"
childcontainer.RegisterType<ISomeDummyDisposableService, SomeDummyDisposableService>(new HierarchicalLifetimeManager());
//this is how to use ViewModel 1st Unity registration
childcontainer.RegisterTypeForNavigationWithChildContainer<ChildContainerDummyViewModel>();
UriQuery parameters = new UriQuery();
parameters.Add("ID", childContainerCounter++.ToString());
var uri = new Uri(typeof(ChildContainerDummyViewModel).FullName + parameters, UriKind.RelativeOrAbsolute);
//use the custom extension methods to specify our own container to use
regionManager.RequestNavigateUsingSpecificContainer("MainRegion", uri, HandleNavigationCallback, childcontainer);
}
Supporting Child Containers For Our PRISM Regions
In order to get PRISM to support child containers all the way down to where it creates the actual item to show within the region that the navigation API is working with, I had to change a few things around a bit. This is one of the things I really like about PRISM is that IT IS TOTALLY EXTENSIBLE.
I started with this extension method
public static class RegionExtensions
{
public static void RequestNavigateUsingSpecificContainer(this IRegion region,
Uri target, Action<NavigationResult> navigationCallback,
IUnityContainer containerToUse)
{
CustomRegionNavigationService moneycorpRegionNavigationService = region.NavigationService as CustomRegionNavigationService;
if (moneycorpRegionNavigationService == null)
throw new InvalidOperationException(
"RequestNavigate that takes a container may only be used with a CustomRegionNavigationService");
((CustomRegionNavigationService)region.NavigationService).RequestNavigate(
target, navigationCallback, containerToUse);
}
}
I then modified the following PRISM classes (they are too big to list here but I will show the most relevant parts, if possible).
IRegionNavigationService
(too much code to show, see the demo code). This class is the main hook into the PRISM navigation API. I simply modified it to have extra methods that would allow a child container to be supplied, that would be used for the navigation / dependency resolutionIRegionNavigationContentLoader
this class is the class that actually loads the item for the navigation (so this could be a view or a viewmodel, so it was clear to me that I needed to play with this guy in order to get him to use a child container)
These custom implementations override the default PRISM ones thanks to the following bootstrapper code:
protected override void ConfigureContainer()
{
base.ConfigureContainer();
//custom region stuff to support child container navigation
Container.RegisterType<IRegionNavigationContentLoader, CustomRegionNavigationContentLoader>(new ContainerControlledLifetimeManager());
Container.RegisterType<IRegionNavigationService, CustomRegionNavigationService>(new ContainerControlledLifetimeManager());
}
This is the most relevant method of the modified
IRegionNavigationContentLoader
class (though make sure you check out the
code for the rest)
/// <summary>
/// Provides a new item for the region based on the supplied candidate target contract name.
/// </summary>
/// <param name="candidateTargetContract">The target contract to build.</param>
/// <returns>An instance of an item to put into the <see cref="IRegion"/>.</returns>
protected virtual object CreateNewRegionItem(string candidateTargetContract, IUnityContainer containerToUse)
{
object newRegionItem;
try
{
if (containerToUse == null)
{
newRegionItem = this.serviceLocator.GetInstance<object>(candidateTargetContract);
}
else
{
newRegionItem = containerToUse.Resolve<object>(candidateTargetContract);
}
}
catch (ActivationException e)
{
throw new InvalidOperationException(
string.Format(CultureInfo.CurrentCulture, "Can not create navigation target {0}", candidateTargetContract),
e);
}
return newRegionItem;
}
With all this in place, we now have the following 2 things:
- You can go ViewModel first, which is what developers love and want to use
- You can still use
VisualState
(s) which is what designers love and want to use
Happy days, I was pleased that this worked out.
That's It
Although this is not a massive article, and it focuses on a rather niche area of WPF / Silverlight development, I do think this sort of approach would be just as valueable when developing a navigation system for Windows 8, that might have to rely on you disposing of views, say when you leave a navigation frame of some sort.
In fact my next series of articles will actually be on using 2 of the better MVVM frameworks out there for Windows 8, so I will be able to put this idea through its paces in a framework that is not strictly made up of composite views (which PRISM obviously supports)
After that I am going to have a break from UI stuff for a bit, and concentrate on some Azure and Powershell, as these 2 things have been on my back burner for quite a while now.
As always if you think this article was useful, or you liked it, any votes/comments would be most welcome.