|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
![]() Are you curious about how to create WPF applications that are modular, maintainable, and easy to unit test? Do you want some tips on how to avoid ending up in Spaghetti Hell during your next WPF project? Do you think that bowls make lousy hats? Read on… Table of Contents
IntroductionAt the time of this writing, the Windows Presentation Foundation (WPF) is a relatively new platform for creating Windows desktop applications. There is not too much available in the way of guidance for developers to follow when they need to “port” their existing application design skills and best practices to WPF. One critical, yet scarcely reviewed, topic is how to leverage the features and powers of WPF to create unit testable applications. This article begins the process of filling that void, by introducing techniques I developed and evolved while working on several real WPF applications over the course of two years. BackgroundThis article has been bouncing around in my head for a long time, just itching to get out. Initially I wanted to focus solely on how to implement the Model-View-Controller pattern in WPF, but eventually I realized that there is already some great material on the Web about that topic. This article introduces how to implement MVC with WPF, but that is merely a prerequisite for explaining how the application’s unit tests work. Toward the end of this article, I introduce the Model-View-ViewModel pattern that some very smart folks at Microsoft invented specifically for WPF. MVVM is a powerful and useful alternative to MVC when designing a WPF application. Common Design ConcernsAt the end of the day, we all want the same basic things out of our application designs, regardless of the UI platform. This section briefly reviews some of those common concerns, simply to provide context for the rest of this article. Nothing in this section is new or specific to WPF application design. Any sensible software architect values modularity. Modular systems are made of self-contained units of functionality that have well defined relationships with each other. Changes made to one module should have a limited and predictable ripple effect on other, closely related, modules. An interface or base class often formally defines the relationship between two modules. One great way to achieve modularity is by creating a layered architecture. This means that the various logical aspects of a system decompose into logical tiers, such as a data access layer, a domain/business layer, a presentation layer, etc. Each layer encapsulates certain concerns into a reusable and generalized abstraction, upon which the other tiers can depend. For example, the data access layer might be the only place in the system that actually reaches out and talks to a database. All other layers call into the data access layer, directly or indirectly, to perform data retrieval and persistence. In a properly layered architecture, it is possible to achieve loose coupling between the user interface markup/code and the application logic that responds to user interaction. Having the application logic loosely coupled to the UI is beneficial in several ways. Replacing the UI at either compile time or runtime becomes easy to achieve, thus allowing visual designers to easily create and incorporate new views for the application. Maintaining and learning the application’s code becomes easier because the application logic is separate from the details of how the UI happens to be constructed. Another great advantage to loosely coupling the application logic to the user interface is that it becomes simple to create unit tests that exercise application functionality without the entanglement of UI-specific concerns. This enables the development team to create a suite of unit tests that prove invaluable for automated regression testing, thus enabling the Quality Assurance team to focus on higher level testing than simply ensuring that the basic unit-level functionality still works properly. Lower That FlamethrowerIf you put ten software architects into a room and have them discuss what the Model-View-Controller pattern is, you will end up with twelve different opinions. In the next section of this article, I provide my definition of MVC. Some of the purists out there will inevitably have qualms with what I refer to as “MVC”. Feel free to leave a flaming comment on the message board at the bottom of this Web page. I will gladly entertain different perspectives on what MVC means, but keep in mind that I do not care. I am too busy getting work done to care about the “philosophically ideal” definition and implementation of MVC. If I must subscribe to some philosophy on this, I agree with Dr. WPF that the ideal pattern is M-V-poo. Introducing MVCThe Model-View-Controller pattern is certainly nothing new. It has been around for decades. There have been many variations of it and many articles, book chapters, and blog posts written about it. The original impetus for the pattern was to help intelligently structure an application to handle low-level user input, back when UI controls were quite primitive. As the developer-friendliness and richness of UI controls has improved over time, MVC has taken on broader meaning. These days MVC generally refers to the separation of concerns wherein you have a Model to represent the logic and data of the problem domain, a View to display the Model on-screen, and a Controller to handle user interaction events and inform the Model when processing needs to occur. The Controller is interested in user interaction; such as keyboard, mouse, or stylus input; for an entire form or user control…not just one button or dropdown. Now that I have regurgitated a generic explanation of MVC, let us see what it really means in practice. Previously we saw how loosely coupling application logic to the UI has many desirable advantages. What we did not review was how to implement that loose coupling. As you have probably guessed by now, the MVC pattern is a fantastic way to do it. The essence of a loosely coupled system boils down to one (bizarre) term that I coined: referencelessness. A loosely coupled system does not have unnecessary object references directly connecting its major functional components. Where there are direct references between parts of the system, they should often be expressed as interfaces or abstract base classes, to allow for dependency injection and object mocking (more on that later). The only places in the system where it makes sense to have a direct reference between two components is where the Controller references the Model, and possibly between the Controller and View in certain situations. As we examine how the demo application uses MVC, the reasoning behind these statements will become apparent. Implementing MVC in WPFThis section reviews the essential features of WPF that we can use to promote referencelessness and modularity in our WPF applications. In subsequent sections, we put these tools to use and see a real example of how the pieces fit together to form a simple infrastructure on top of which real applications can thrive. There are four fundamental pillars involved with implementing the MVC pattern in a WPF application. Here is a brief overview of each pillar, and what they contribute toward the overarching goals of referencelessness and modularity. Routed CommandsThe Command pattern is a well-established way to represent and execute actions in an application. A command represents an action that the application can perform. When various parts of the app need to perform that action, they ask the command to execute. Often the command object contains the execution logic, which encapsulates the entire operation into one convenient and reusable object. Commands are a first-class feature in WPF. Implement the When something in an application requests a routed command to execute, the command’s For example, suppose you have a WPF application used for editing documents. The application has a Save button on the toolbar, but if no document is open, WPF will automatically disable the Save button for you (assuming the button is wired to execute the Save command and the application’s Save command Elements in the element tree can establish event handlers for a routed command’s events by creating a Routed commands help us loosely couple the Controller to a View. Controls and elements in a View can execute routed commands for which the Controller has execution logic baked in. This means that the View does not reference its Controller, nor does the Controller care about what in the View executed the command. The Controller and View relate only via these “semantic identifiers”, not direct object references. Learn more about commanding in WPF here. Data BindingData binding is a way to automate the transfer of data between the Model and View. WPF provides a very comprehensive and flexible binding system, based entirely on runtime type information (a.k.a. .NET reflection). Since bindings are inherently late-bound, the View does not need a direct reference to the Model at compile time. This allows you to associate a View to the Model purely by the names of entities in the Model’s API, instead of to the class’s properties themselves. This layer of indirection provided by data binding allows the Model and View to remain loosely coupled. The Model can later be swapped out with a façade if necessary, and the View can easily be replaced with a new or culture-specific version either at runtime or compile time. If your Model objects have their properties set by the application at runtime, they should implement the Sometimes the data exposed by the Model needs to be transformed, formatted, or combined before being shown to the user. Conversely, sometimes the data entered by the user needs to be massaged before being sent back to the associated Model object. These types of data conversions are handled by creating a class that implements You can validate the data passed between the View and Model in several ways. Which technique is appropriate to use is highly dependent on the specifics of the scenario and requirements. The worst option is to create In general, validation logic belongs in the domain/business layer. As of .NET 3.5, the WPF binding system works with the Collection ViewsWhen you bind to a collection in WPF, what you are really binding to is a view wrapped around the collection. WPF creates an In the WPF/MVC world we are creating here, the collection view is a crucial ingredient to help decouple the Controller from a View. In order for the Controller to not need a reference to a View, it can instead reference the collection view wrapped around a list of Model objects shown in the UI. If the View happens to display that list of Model objects in a One shortcoming of this technique is that ResourcesThis might seem like a strange addition to our list of “fundamental pillars” for implementing WPF apps that are easy to unit test. You might be wondering how the resource system is relevant, since it typically just stores styles, templates, brushes, etc. It turns out that you can use the WPF resource system as a simple and lightweight dependency injection Framework. You can store any type of object in a resource dictionary, and every element has a resource dictionary exposed by its The Demo AppBefore we dive into the demo application code (which is available to download at the top of this article), let us first see what the demo looks like. The application allows you to look at some x-ray images. The main screen consists of a simple list of x-rays and a button that, when clicked, opens a dialog displaying the image associated with the selected x-ray. This picture shows what the app looks like after clicking the ‘View X-Ray’ button when the selected x-ray is the first item in the list.
Notice in the next screenshot that once the image viewer dialog is closed, the x-ray that you viewed has its display text altered to indicate to the user that he has already viewed this x-ray image.
Please bear in mind that I have absolutely no experience with programming in the medical industry, no knowledge of x-rays, and am not suggesting that this is the “right” approach to take when designing a user interface for viewing x-rays. I just like to look at x-ray images of skulls! How the Demo App WorksThis section reviews how most of the demo application puts the features of WPF to use to implement MVC. Using the MVC pattern for this simple demo application is most certainly overkill, considering that the application is extremely simple and stupid. This is the perennial problem with creating demo apps: they need to be simple enough not to obfuscate the important information presented, but complicated enough to demonstrate the topic at hand. Consider this demo as simply a starting point from which the development of real applications can unfold. Architecture OverviewThe main window is public XrayWindow()
{
InitializeComponent();
XrayCollection xrays = XrayCollection.Load();
// Create the controller that
// handles user interaction.
_controller = new XrayWindowController(this, xrays);
// Use the list of Xray objects as
// this Window's data source.
base.DataContext = xrays;
}
Walking Through the CodeNow we will examine how the list of x-rays is displayed and what is in place to show an x-ray image when the user clicks the ‘View X-Ray’ button. Here is the XAML of the <UserControl
x:Class="TestableXrayDemo.Views.XrayWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:demo="clr-namespace:TestableXrayDemo"
xmlns:model="clr-namespace:TestableXrayDemo.Model"
xmlns:views="clr-namespace:TestableXrayDemo.Views"
>
<UserControl.Resources>
<!--
The resources were omitted for the sake of clarity.
-->
</UserControl.Resources>
<!--
These are the controls seen in the main Window.
-->
<DockPanel Margin="2">
<Button
Command="{x:Static demo:Commands.ShowSelectedXray}"
Content="View X-Ray"
DockPanel.Dock="Bottom"
HorizontalAlignment="Center"
Margin="0,4"
/>
<ListBox
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=.}"
/>
</DockPanel>
</UserControl>
The UI consists of a Now focus on the public static class Commands
{
/// <summary>
/// Executed when the selected Xray's image should be displayed.
/// </summary>
public static readonly RoutedUICommand ShowSelectedXray;
static Commands()
{
ShowSelectedXray = new RoutedUICommand(
Resources.ShowSelectedXrayCommandText,
"ShowSelectedXray",
typeof(Commands));
}
}
At this point, we have seen how the View references a custom routed command and the command definition, but we still have not seen what happens when the command executes. That is a two-part process, starting in <Window
x:Class="TestableXrayDemo.XrayWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:demo="clr-namespace:TestableXrayDemo"
xmlns:views="clr-namespace:TestableXrayDemo.Views"
FontSize="20"
MaxWidth="800" MaxHeight="1000"
MinWidth="250" MinHeight="200"
SizeToContent="WidthAndHeight"
Title="X-Ray Demo"
WindowStartupLocation="CenterScreen"
>
<!--
Establish handlers for the ShowSelectedXray command's events.
-->
<Window.CommandBindings>
<CommandBinding
Command="{x:Static demo:Commands.ShowSelectedXray}"
CanExecute="ShowSelectedXray_CanExecute"
Executed="ShowSelectedXray_Executed"
/>
</Window.CommandBindings>
<!--
This is the View applied to the Model.
-->
<views:XrayWindowView />
</Window>
The window has a void ShowSelectedXray_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = _controller.CanShowSelectedXray;
}
void ShowSelectedXray_Executed(object sender, ExecutedRoutedEventArgs e)
{
_controller.ShowSelectedXray();
}
As you can see above, Now let us examine how the Controller satisfies the user’s request to view the selected x-ray image. This code resides in public bool CanShowSelectedXray
{
// Return true if there is a selected Xray in the UI.
get { return _xraysView.CurrentItem != null; }
}
The If the user can view an image and clicks the ‘View X-Ray’ button, the following method is invoked. public void ShowSelectedXray()
{
#region Disclaimer
// This method does not perform any null checks
// for the sake of clarity and simplicity. In a
// real app a method like this should more robust.
#endregion // Disclaimer
// Get the Xray object selected in the UI.
Xray selectedXray = _xraysView.CurrentItem as Xray;
// Find the UI object for displaying x-ray images.
// If running in a unit test we get a mock object.
IXrayImageViewer xrayViewer =
_xrayWindow.FindResource("VIEW_XrayImageViewer")
as IXrayImageViewer;
Uri imageLocation = GetXrayImageLocation(selectedXray);
xrayViewer.ShowImage(imageLocation);
// The UI detects the new value of HasBeenViewed
// because Xray implements INotifyPropertyChanged.
if (!selectedXray.HasBeenViewed)
selectedXray.HasBeenViewed = true;
}
There are two points of interest in this method. The Controller makes use of the WPF resource system to get an object it can use to display the x-ray image. It does this by calling <Application
x:Class="TestableXrayDemo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:TestableXrayDemo.Views"
StartupUri="XrayWindow.xaml"
>
<Application.Resources>
<!--
This is the Window used to display x-ray images.
It is marked as not being shared because you cannot
show a Window that has already been closed. Making
it unshared means a new instance is created every
time it is requested by the XrayWindowController.
-->
<views:XrayImageViewer
x:Key="VIEW_XrayImageViewer"
x:Shared="False"
/>
</Application.Resources>
</Application>
After the call to public bool HasBeenViewed
{
get { return _hasBeenViewed; }
set
{
if (value == _hasBeenViewed)
return;
_hasBeenViewed = value;
this.OnPropertyChanged("HasBeenViewed");
}
}
The <!--
This alters an Xray's display text
after its image has been viewed.
-->
<Style x:Key="XrayTextBlockStyle" TargetType="TextBlock">
<Style.Triggers>
<DataTrigger
Binding{Binding Path=HasBeenViewed}"="
Value="True"
>
<Setter Property="FontStyle" Value="Italic" />
<Setter Property="Foreground" Value="Gray" />
</DataTrigger>
</Style.Triggers>
</Style>
Unit Testing the Model and ControllerAt this point, we have seen how to create a WPF application that uses the MVC pattern. A result of this design practice is that we have a clean separation between the application’s data model, interaction logic, and user interface. Now that we have a loosely coupled system built of modular components, we can easily create unit tests that focus on the application’s interaction logic and data model. In a properly designed system, there should be no need to unit test the UI since it simply “consumes” the Controller. We want our unit tests and UI to both “consume” the Controller, so that testing the application logic programmatically is the same as testing it manually. The demo application has a separate project that contains unit tests. I decided to use the unit testing Framework built into Visual Studio 2008 just for the sake of convenience. These tests would work fine in NUnit, or any other Framework you happen to use. They do not depend on any esoteric features of the built-in Visual Studio 2008 test Framework. Testing the ModelThe test for the [TestMethod]
public void HasBeenViewedTest()
{
DateTime creationDate = new DateTime();
string fileName = string.Empty;
XraySide side = XraySide.Front;
Xray xray = new Xray(creationDate, fileName, side);
Assert.IsFalse(xray.HasBeenViewed, "HasBeenViewed should return false now.");
bool eventIsCorrect = false;
xray.PropertyChanged +=
delegate(object sender, PropertyChangedEventArgs e)
{
eventIsCorrect = e.PropertyName == "HasBeenViewed";
};
xray.HasBeenViewed = true;
Assert.IsTrue(
eventIsCorrect,
"Setting HasBeenViewed to true did not raise PropertyChanged event correctly.");
Assert.IsTrue(xray.HasBeenViewed, "HasBeenViewed should return true now.");
}
Simulating User InteractionThe more interesting unit tests are those that exercise the [TestMethod]
public void CanShowSelectedXrayTest()
{
XrayWindow xrayWindow = new XrayWindow();
XrayCollection xrays = xrayWindow.DataContext as XrayCollection;
XrayWindowController target = new XrayWindowController(xrayWindow, xrays);
ICollectionView xraysView = CollectionViewSource.GetDefaultView(xrays);
xraysView.MoveCurrentToPosition(-1);
Assert.IsFalse(
target.CanShowSelectedXray,
"Should not be able to show an image since no Xray is selected.");
// Setting the position to zero is essentially the
// same as selecting the first Xray in the ListBox.
xraysView.MoveCurrentToPosition(0);
Assert.IsTrue(
target.CanShowSelectedXray,
"Should be able to show an image since an Xray is selected.");
}
Now we can start to see the beauty of how implementing a decoupled system allows for rich unit testing capabilities. Since the Controller uses Injecting Mock ObjectsThe last unit test is slightly more involved. In this test, we want to verify that the Controller’s This raises a slight problem. A unit test suite should be able to run to completion without requiring user interaction. This enables the tests to run on a continuous integration server, and prevents running them on your local machine from being an annoyance. If we were to test the Controller’s As the title of this sub-section suggests, this is about a unit test that injects a mock object into the method under test. As seen previously, the public void ShowSelectedXray()
{
#region Disclaimer
// This method does not perform any null checks
// for the sake of clarity and simplicity. In a
// real app a method like this should more robust.
#endregion // Disclaimer
// Get the Xray object selected in the UI.
Xray selectedXray = _xraysView.CurrentItem as Xray;
// Find the UI object for displaying x-ray images.
// If running in a unit test we get a mock object.
IXrayImageViewer xrayViewer =
_xrayWindow.FindResource("VIEW_XrayImageViewer")
as IXrayImageViewer;
Uri imageLocation = GetXrayImageLocation(selectedXray);
xrayViewer.ShowImage(imageLocation);
// The UI detects the new value of HasBeenViewed
// because Xray implements INotifyPropertyChanged.
if (!selectedXray.HasBeenViewed)
selectedXray.HasBeenViewed = true;
}
When the application is running normally the call to /// <summary>
/// An implementation of IXrayImageViewer
/// for unit testing purposes.
/// </summary>
private class MockXrayImageViewer : IXrayImageViewer
{
public bool ShowImageWasCalled = false;
public void ShowImage(Uri imageLocation)
{
this.ShowImageWasCalled = true;
}
}
Here is the test method that creates the mock object and then tests the Controller with it: [TestMethod]
public void ShowSelectedXrayTest()
{
XrayWindow xrayWindow = new XrayWindow();
// Add a fake image viewer to the window's resources
// so that the controller being tested can use it.
MockXrayImageViewer mockViewer = new MockXrayImageViewer();
xrayWindow.Resources.Add(
"VIEW_XrayImageViewer",
mockViewer);
// Create the collection of x-rays and its default view.
XrayCollection xrays = xrayWindow.DataContext as XrayCollection;
ICollectionView xraysView = CollectionViewSource.GetDefaultView(xrays);
// Select the first Xray object.
Xray selectedXray = xrays[0];
xraysView.MoveCurrentTo(selectedXray);
// Perform the test.
XrayWindowController target = new XrayWindowController(xrayWindow, xrays);
target.ShowSelectedXray();
Assert.IsTrue(mockViewer.ShowImageWasCalled, "ShowImage should have been invoked.");
Assert.IsTrue(selectedXray.HasBeenViewed, "HasBeenViewed property should be true.");
}
When you run this test suite, you will see no windows popping open. Since the Controller calls Introducing MVVMEven though this article focuses on how to use my version of MVC for WPF, it is worth noting that I am not the first person to explore this territory. Some very smart people at Microsoft devised a variant of MVC tailor-made for WPF. They dubbed it the Model-View-ViewModel pattern, sometimes called the DataModel-View-ViewModel pattern. In this pattern, the Controller morphs into the ViewModel. The ViewModel is essentially a fancy WPF-friendly wrapper around the underlying Model. The ViewModel exposes collections of data as observable collections, which enable rich data binding support. It also allows you to place domain validation and request processing in a layer that sits between the View and Model. When you use MVVM the View binds to the ViewModel, not the Model. In that sense, the ViewModel acts as an adapter between the “real” model and the user interface. To learn a lot more about MVVM, check out these links:
Revision History
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||