C# Corner
WPF and Inversion of Control
Use Inversion of Control to decouple views and viewmodels in WPF.
In this article, I'll look at ways to combine Windows Presentation Foundation (WPF) and the Inversion of Control (IoC) container Castle Windsor to decouple Views and ViewModels within Model-View-ViewModel (MVVM) to make unit testing easy and painless.
IoC Containers
I'm a big fan of IoC containers. They make dependency management much easier and give me a clean, decoupled architecture. In fact, I devoted an entire article to IoC back in the August 2010 issue. As I did then, I'll be using the Windsor container. Other IoC containers should have similar functionality as described in this article.
Bringing IoC to WPF
When I first started learning WPF, I simply set my DataContext to my ViewModel in the window constructor:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowVM();
}
}
This worked OK in the beginning. As my application grew, I wanted to take advantage of IoC in my ViewModel; there were repositories to inject as well as other dependencies. Because of these and other reasons, I needed to move my ViewModels into my IoC container.
Moving the ViewModel into the container was trivial. But how would I resolve the ViewModel at runtime? I could use a ServiceLocator inside my WPF Window constructor, but I don't like the ServiceLocator pattern, so I wouldn't go down that route. What if I put my WPF Window into the container and let the ViewModel be an automatically resolved dependency, like so:
public partial class MainWindow : Window
{
public MainWindow(MainWindowVM viewModel)
{
InitializeComponent();
this.DataContext = viewModel;
}
}
With this approach, I just added all the ViewModels and Views (WPF Windows) into the container and let Windsor resolve them along with all of their dependencies. But I still had to remember to set the DataContext in the constructor of all my Views. Because it was one additional thing I could forget, I needed to find a way to fix it.
Custom Component Activators in Windsor
Windsor, as well as other IoC containers, is highly customizable. By default, when Windsor activates one of the components you've registered, it first resolves and creates all dependencies of the component, and then basically does a "new" to create the component. I needed to hook into that process and add one additional step: assigning the ViewModel to the DataContext.
Windsor makes this very easy by allowing you to subclass the DefaultComponentActivator, the part of Windsor responsible for activating components. There's a CreateInstance method you can override to provide the additional functionality. Here's the method inside my new activator I called "WPFWindowActivator":
protected override object CreateInstance(CreationContext context,
object[] arguments, Type[] signature)
{
var component = base.CreateInstance(context, arguments, signature);
// Assign our DataContext here.
return component;
}
As you can see, CreateInstance gives you all the constructor arguments. But the WPF Window could (and probably will) have more than one argument. How do you know which one is the ViewModel? Sure, by convention, it will probably always be the first one, but you don't want to rely on that behavior. Instead, create an IViewModel marker interface. This interface has no methods -- just use it to mark the ViewModels for Windsor:
public interface IViewModel
{
}
public class MainWindowVM : IViewModel
{
}
Now, assigning the DataContext to the ViewModel is trivial, as shown in Listing 1.
This call gets made from the WPFWindowActivator.Create-Instance right after creating the component. Inside here, a check is added at the beginning to make sure you're working with a bindable WPF object (FrameworkElement). If you are, you find the first argument that implements IViewModel (assuming there's only ever one) and assign the DataContext to that argument.
When registering a WPF Window, you need to tell it to use your custom activator. This is easy with the Windsor fluent interface:
container.Register(Component.For<MainWindow>().
Activator<WPFWindowActivator>());
But now you have to do this with every WPF Window. It would be easier to automate the assigning of the WPFWindowActivator.
Windsor Facilities
As I noted earlier, Windsor extensibility can help here, too. Windsor supports what it calls "facilities." A facility is the main way of extending the Windsor container. A Windsor facility can be notified whenever a component has been registered -- not created, but simply registered with the container. That would be a perfect time to assign a custom activator.
Like I did with IViewModel, I'll create a marker interface for Views called IView. This means I can assign the WPFWindowActivator to any IView-based type. The facility is shown in
Listing 2.
I check if the component just registered with Windsor is one of my Views (an IView). If so, I make sure a custom activator hasn't already been assigned before assigning my WPFWindowActivator.
It's important to add this facility to Windsor before registering any components. As you can see in Listing 2, I'm hooking in to the facility's ComponentModelCreated event. This event is fired as soon as a component model (based on the registration of the component) has been created. Adding the facility after registering the components wouldn't assign my custom activator.
Finally, gluing all of this into Windsor becomes even easier now that I've created marker interfaces for Views and ViewModels. You can tell Windsor to register all IView-based and all IViewModel-based types instead of doing them one-by-one. This means I don't have to revisit the component registration code every time I add a new WPF Window or ViewModel to my application.
The App.xaml.cs for configuring the Windsor container is shown in Listing 3.
My MainWindow needs to be modified to implement an IView- based interface (I'll call it IMainView), and I need to remove the "StartupUri" attribute from App.xaml -- I'll be creating the component through Windsor now. By overriding OnStartup inside App.xaml.cs, I can complete the integration of WPF and Windsor, like so:
protected override void OnStartup(StartupEventArgs e)
{
var main = container.Resolve<IMainWindow>();
main.ShowDialog();
}
This custom activator along with the facility makes WPF development with IoC a breeze:
- ViewModels are fully integrated with Windsor, so they get all those benefits -- dependency injection, custom lifestyles and so on.
- Views are fully integrated with Windsor, along with automatic assignment of the ViewModel to the DataContext.
- The marker interfaces of IView and IViewModel make registration of all components in the container much easier and virtually maintenance-free.
- ViewModel is fully decoupled from the View and can be tested in isolation. It also encourages reuse of my ViewModel, as it has no WPF references.
Launching Other Views
All this code was working fine until I needed to display another WPF Window. This is usually accomplished by doing a "new" on the window and calling ShowDialog. I don't want to use "new" anymore because I want my container to resolve the Window. I also don't want to globally expose the container and sprinkle container.Resolve calls all over my code (this is almost as bad as the Service Locator pattern).
You can use the factory pattern to control creation of objects. (Read about the factory pattern in a series of articles I wrote earlier this year, available here.) A factory allows you to implement your own process for creating other objects. During your tests, you can create a stub factory that returns other stubbed objects. During runtime, you can use a factory that resolves components from Windsor.
My factory will contain two methods -- one for creating objects without any parameters, and one that allows for creating objects with parameters:
public interface IViewFactory
{
T CreateView<T>() where T : IView;
T CreateView<T>(object argumentsAsAnonymousType) where T : IView;
}
Note that I'm putting a constraint on my generic type: It must be of type IView. This makes sure I only use this factory for my WPF Views registered in Windsor. My Windsor implementation of this is straightforward, as shown in Listing 4.
My WindsorViewFactory accepts an instance of the Windsor container. This means I'll actually register the container inside itself because it's a dependency for other objects. The WindsorViewFactory will be registered in Windsor, as any ViewModel that needs to launch another View will have a dependency on IViewFactory.
I now have a factory that will return an IView-based window for display. But with the IView having no methods, it won't do me much good. I'll update my base IView interface to contain the same ShowDialog signature used by the WPF Window class. By matching this simple signature (which doesn't contain any WPF-specific types), I don't need to implement any new methods in my windows:
public interface IView
{
bool? ShowDialog();
}
Now that I've abstracted the creation of Views, let's see how this would work in a real application. The sample application uses the ICommand interface and one of the many available implementations of DelegateCommand.
First, I'll update MainWindowVM to have a dependency on IViewFactory. Because my ViewModels are resolved with Windsor, it will plug in the Windsor implementation at runtime:
public class MainWindowVM : IViewModel
{
private readonly IViewFactory viewFactory;
public MainWindowVM(IViewFactory viewFactory)
{
this.viewFactory = viewFactory;
}
...
}
My MainWindowVM also exposes a Launch command to which a button can bind. This will be used to launch a second window:
public ICommand Launch
{
get { return launchCommand ??
(launchCommand = new DelegateCommand(ShowSecondWindow)); }
set { launchCommand = value; }
}
private void ShowSecondWindow()
{
var view = viewFactory.CreateView<ISecondView>();
view.ShowDialog();
}
As you can see, the CreateView call doesn't use any concrete types. I've created the IView-based interface "ISecondView" for the other window (which is called SecondWindow.xaml). I previously created an IView-based interface for the main View as well:
public interface ISecondView : IView
{
}
public interface IMainView : IView
{
}
public partial class SecondWindow : Window, ISecondView
{
...
}
public partial class MainWindow : Window, IMainView
{
...
}
This completes most of the architecture of launching a new window from inside a ViewModel -- all without any direct references to a WPF-based window and all using interfaces that I can mock at test time.
The earlier plumbing to hook all this up needs a few changes. First, I need to register both the container itself and the Windsor implementation of my IViewFactory. This can be accomplished by adding two more component registration calls inside the previous container.Register block, as shown in Listing 5.
The two bolded lines in Listing 5 are the new lines.
Then I just need to change the OnStartup override in App.xaml.cs.
Because I now have a factory that's used to create other Views, I should use it (for consistency) instead of resolving the IMainView directly with Windsor, like so:
protected override void OnStartup(StartupEventArgs e)
{
var factory = container.Resolve<IViewFactory>();
var main = factory.CreateView<IMainView>();
main.ShowDialog();
}
Interacting with the View
There may be times when the ViewModel wants to "talk" or interact with the View. Direct manipulation of the View should be discouraged. It binds the ViewModel to a specific View and greatly reduces usability while making testing more difficult.
Fortunately, the View reference my ViewModels obtain from the IViewFactory.CreateView is only an interface. If the View needs to return some data, I can simply add the necessary property get/set definitions on the IView-based interface, implement them in the WPF window and then access them from the ViewModel after the ShowDialog completes.
There are times, however, when a ViewModel may want to talk directly to the View to which it's bound. Suppose the ViewModel has an ICommand to display a help page or navigate to a URL. Obviously, you don't want the ViewModel to actually perform those things, as that would make unit testing more difficult. You could use the previously detailed approach and define additional methods on the IView-based interface for the View. But how does the ViewModel get access to the View?
What I'd like to do is kind of the opposite of what I did earlier with my custom activator. There, I took a WPF window and assigned its DataContext to the ViewModel. The opposite would result in passing a WPF window reference to the ViewModel.
This is pretty straightforward with reflection and a little bit of convention. First, I'll state that if a ViewModel contains a settable IView-based property, my custom activator will set it to the WPF window it just created. The method to accomplish this is shown in Listing 6.
I'll call this from the existing "AssignViewModel" right after I assign the ViewModel to the DataContext.
To see how this works, I'll assume there's an ICommand on the MainWindowVM that's supposed to launch a help file for the user. I'll update my IMainView interface to support this:
public interface IMainView : IView
{
void ShowHelp();
}
Implementation of this method in the WPF window is irrelevant to this article and is left to the reader. The WPF window may launch a CHM file for viewing, or a browser to a URI. I (along with our ViewModel) don't care.
Now I'll create a writable IMainView property on my ViewModel.
I usually call this property "ParentView," as it's kind of the "parent" of the ViewModel:
public IMainView ParentView { get; set; }
All I need to do is define it. The AssignParentView method I added to my custom activator will handle setting this property. I can now access it from within my ViewModel:
public ICommand ShowHelp
{
get { return showUrlCommand ??
(showUrlCommand = new DelegateCommand(ShowUserHelp)); }
set { showUrlCommand = value; }
}
private void ShowUserHelp()
{
ParentView.ShowHelp();
}
Again, my ViewModel is completely decoupled from the WPF window. In fact, it's totally decoupled from WPF. I could easily use this same ViewModel in a Silverlight application by simply creating a Silverlight User Control that implemented the IMainView interface.
Testing the ViewModel
I've put a lot of work into my WPF/IoC architecture. One of the benefits of a good design is that it's easy to test. Let's see how I can test my ViewModel. For these tests, I'll be using MSTest and the open source mocking framework Moq.
The first test needs to ensure that when the Launch command is executed, our SecondWindow is displayed. Remember, I'm only working with interfaces here, so I don't need to worry about actually displaying (and closing) a WPF window inside my tests.
I begin by creating my mock IViewFactory and my mock ISecondView. I also set up the factory's CreateView to return my mocked ISecondView and set up a verifiable condition for the ShowDialog method. This allows us to verify that the method was actually called:
[TestMethod]
public void Launch_should_display_SecondWindow()
{
var factory = new Mock<IViewFactory>();
var secondView = new Mock<ISecondView>();
factory.Setup(f => f.CreateView<ISecondView>()).Returns(secondView.Object);
secondView.Setup(v => v.ShowDialog()).Verifiable();
}
With all of my dependencies mocked, I can now create the ViewModel and perform the actual test:
var viewmodel = new MainWindowVM(factory.Object);
viewmodel.Launch.Execute(null);
To make sure everything worked I need to verify the secondView state:
secondView.VerifyAll();
If the ShowDialog method is never called on the secondView mock, an exception is thrown and the test fails. Check out the sample code for this article, where I also include a test to make sure the View ShowHelp method is called when the ViewModel ShowHelp ICommand is executed.
Flexible, Easy Testability
As you can see, WPF and IoC can work well together. By using a few hooks in Windsor, you get a decoupled architecture that's flexible and easy to test. Extending the application by adding more Views and ViewModels doesn't involve a lot of maintenance, either. I've been using this approach on a midsize WPF application and it has worked out very well.