Here is a link where you can download the
Demo Application
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
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);
}
IMPORTANT :
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.
private void NavigateToChildContainerViewModel(CreateChildContainerViewModelMessage message)
{
var childcontainer = mainAppContainer.CreateChildContainer();
childcontainer.RegisterType<ISomeDummyDisposableService, SomeDummyDisposableService>(new HierarchicalLifetimeManager());
childcontainer.RegisterTypeForNavigationWithChildContainer<ChildContainerDummyViewModel>();
UriQuery parameters = new UriQuery();
parameters.Add("ID", childContainerCounter++.ToString());
var uri = new Uri(typeof(ChildContainerDummyViewModel).FullName + parameters, UriKind.RelativeOrAbsolute);
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 resolution
IRegionNavigationContentLoader
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();
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)
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.
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.