Introduction
This article explains a simple and testable technique for working with message boxes from the ViewModel layer of a WPF or Silverlight application based on the Model-View-ViewModel design pattern. The demo application was created in Visual Studio 2008 with Service Pack 1.
Background
One of the most common questions I see people asking about MVVM application design is how to show a message box from their ViewModel objects. At first glance the question might seem absurd. Just call MessageBox.Show()
, right? In some scenarios, that answer is absolutely correct. That answer falls flat on its face in other scenarios.
Why might calling MessageBox.Show()
from a ViewModel object not work out so well? The two most common issues are custom message boxes and unit testing. I explained the former issue in great detail in my book ‘Advanced MVVM’, so I won’t cover that topic in this article. In case you’re wondering what I mean by a custom message box, the following screenshot from the book’s demo application, BubbleBurst, shows an example:
The latter issue, unit testing, is a more common requirement for many developers. The problem with showing a message box from a ViewModel object is that it might be shown while running your unit tests. This prevents the tests from completing until someone walks over and closes the message box. If your unit tests are running on a build server, which is not monitored by anyone or perhaps does not even have a monitor attached, this can be a serious problem.
The problem we face is twofold. First, how can a ViewModel prompt the user for a decision, such as whether to save their work before the application closes, without jumping through a bunch of hoops? Second, how can we test those ViewModel objects without causing the test suite to be unable to complete due to an open message box?
Service Locator to the Rescue
The solution to the problem is simple: do not call MessageBox.Show()
from a ViewModel object. Instead, provide the ViewModel with a “message box service” object that invokes MessageBox.Show()
. It might not seem like this layer of indirection solves the problem of having message boxes open up while running unit tests, but it can.
The key is that the ViewModel object does not know, or care, about what a message box service does. When running unit tests you can provide the ViewModel with a fake message box service, that does not call MessageBox.Show()
, but simply returns whatever MessageBoxResult
value you specified in advance. The ViewModel object never knows the difference; it just gets a message box service, calls the Show
method, and gets a return value to work with.
What I’m describing here is an example of a technique known as Service Locator. Entire books have been written, frameworks created, wars waged, egos battered, and philosophies based on what Service Locator is and what it is not. So, I’m not going to bother explaining it in depth here. Instead I present you with the following image, which I use as the basis of an analogy.
In this analogy the arm, and the rest of the person’s body attached to it, is your application. Each organ in the body, such as the heart and brain, is analogous to a module or subsystem in the application. For whatever reason, this person’s body needs some radioactive albumin in it. Organs in the body depend on radioactive albumin, which makes that substance a dependency of the organs.
In order to get radioactive albumin to the body’s organs, we put it into the person’s bloodstream, which spreads it around the body. The blood can be thought of as locating whatever dependencies the organs have, or as storing them like a container. Once the blood contains the radioactive albumin, the organs can extract it from the blood as necessary. In order to inject the radioactive albumin into the bloodstream, we rely on something to load/put/inject it into the container from which it can be located, in this case a medical syringe that introduces the substance.
So far the analogy has held together quite well, but the last part I want to mention is a bit of a stretch. One of the important concerns with using Service Locator has to do with when the container is filled with service dependencies. In our medical injection analogy a tourniquet represents the time at which dependencies are placed into the container. Quite often, they are injected into a container before the application’s modules load and try to locate them. In this sense, the fact that the injection occurs on application start-up is like a tourniquet that prevents the rest of the body from causing problems during the injection.
Now let’s see how this applies to working with message boxes from ViewModel objects.
Example Scenario
The demo application discussed in this article is available for download at the top of this page. It contains a very simple application that allows the user to type the name of a person. If the user types in a valid name (i.e. the first and last name are entered) the Save button becomes enabled so that they can save the data. Once the data has been saved, the Save button is disabled again until the name is changed. When the user has entered a valid name but not yet clicked the Save button, if they try to close the window the application shows a message box, asking if they want to save before closing.
The Person
data model class represents a person with a name. It keeps track of whether it has been edited, and it can also validate itself. The following class diagram shows its important members:
An instance of the Person
class is wrapped by PersonViewModel
. That class is responsible for presenting Person
data and allowing it to be saved. It also implements my IScreen
interface, which is used to give the PersonViewModel
a chance to ask the user if unsaved changes should be saved before closing.
One important thing to take note of is that PersonViewModel
derives from ViewModelBase
. That class has two points of interest. It inherits from the ObservableObject
class in my MVVM Foundation library so that it can get support for property change notifications for free. In this demo app there is no need to send property change notifications, but all self-respecting ViewModel base classes must support this feature because most ViewModel objects require it. The other thing that ViewModelBase
does is expose a GetService
method, which we will look at later.
As noted above, PersonViewModel
implements the IScreen
interface so that it will be asked if it can close. By “close” I mean that the View displaying it can be removed from the user interface. When PersonViewModel
is asked if it can close, it checks to see if its Person
data object can be saved. If so, it asks the user what to do, as seen below:
bool IScreen.TryToClose()
{
if (this.CanSave)
{
var msgBox = base.GetService<IMessageBoxService>();
if (msgBox != null)
{
var result = msgBox.Show(
"Do you want to save your work before leaving?",
"Unsaved Changes",
MessageBoxButton.YesNoCancel,
MessageBoxImage.Question);
if (result == MessageBoxResult.Cancel)
return false;
if (result == MessageBoxResult.Yes)
this.Save();
}
}
return true;
}
The method seen above uses the GetService
method of ViewModelBase
in order to get a reference to a message box service. This is where PersonViewModel
’s dependency on IMessageBoxService
is located. The next section of this article explains how the application implements a lightweight service locator to resolve this dependency.
Exploring the Service Container
At this point we have seen how the demo application relies on service location to provide its ViewModel objects with the ability to show a message box. Now let’s turn our attention to how I implemented support for this.
In a large production application I would strongly consider using a pre-existing class or framework for my service locating needs. But for a simple demonstration of a simple application, that would be overkill. So I wrote my own simple locator. It weighs in at just under fifty lines of lightning fast code. The ServiceContainer
class is displayed below:
public class ServiceContainer
{
public static readonly ServiceContainer Instance = new ServiceContainer();
private ServiceContainer()
{
_serviceMap = new Dictionary<Type, object>();
_serviceMapLock = new object();
}
public void AddService<TServiceContract>(TServiceContract implementation)
where TServiceContract : class
{
lock (_serviceMapLock)
{
_serviceMap[typeof(TServiceContract)] = implementation;
}
}
public TServiceContract GetService<TServiceContract>()
where TServiceContract : class
{
object service;
lock (_serviceMapLock)
{
_serviceMap.TryGetValue(typeof(TServiceContract), out service);
}
return service as TServiceContract;
}
readonly Dictionary<Type, object> _serviceMap;
readonly object _serviceMapLock;
}
For the purposes of the demo application, the locks placed around usage of the service map are unnecessary since a service implementation is never replaced after application start-up. I included the locks to help prevent threading issues for people who use this class in a more dynamic system but might forget to add them.
Previously I mentioned that ViewModelBase
has a method called GetService
which resolves service dependencies for its child classes. That method exists so that all ViewModel objects rely on the same strategy for resolving service dependencies. If later on you decide to change the way that those dependencies are located, you only need to update that one base class method. That method is seen here:
public TServiceContract GetService<TServiceContract>()
where TServiceContract : class
{
return ServiceContainer.Instance.GetService<TServiceContract>();
}
So far we have seen how PersonViewModel
relies on a service interface, IMessageBoxService
, to show a message box. We saw that its IScreen.TryToClose
method calls into its base class’s GetService
method, which simply delegates to the GetService
method of ServiceContainer
. Next up, we examine how the service container is populated and how this design can be leveraged to create good unit tests.
Test-time
Since PersonViewModel
relies on a service dependency to show a message box, we can write unit tests that avoid causing message boxes to be shown. This can be achieved by following three simple steps.
First we create a class that implements the IMessageBoxService
interface. This class is only used for testing purposes, which is why I put it in the UnitTests project. The MockMessageBoxService
class is shown below:
class MockMessageBoxService : IMessageBoxService
{
public MessageBoxResult ShowReturnValue;
public int ShowCallCount;
public MessageBoxResult Show(
string message,
string title,
MessageBoxButton buttons,
MessageBoxImage image)
{
++ShowCallCount;
return this.ShowReturnValue;
}
}
Next we need to put an instance of MockMessageBoxService
into the same ServiceContainer
that a PersonViewModel
uses to locate its service dependencies. To ensure that the tourniquet is tied nice and tight, we can perform this step in a method decorated with AssemblyInitializeAttribute
. That attribute is part of the Visual Studio unit testing framework. It marks a method that should be executed once before any tests in the assembly are run.
[TestClass]
static class MockServiceInjector
{
[AssemblyInitialize]
public static void InjectServices(TestContext context)
{
ServiceContainer.Instance.AddService<IMessageBoxService>(
new MockMessageBoxService());
}
}
The last step is to write unit tests that exercise PersonViewModel
. These tests can verify that the TryToClose
method of PersonViewModel
is behaving itself.
[TestMethod]
public void ShowsMessageBoxWhenClosedAndCanSave()
{
var personVM = new PersonViewModel(new Person
{
FirstName = "Josh",
LastName = "Smith"
});
var personScreen = personVM as IScreen;
var msgBox =
personVM.GetService<IMessageBoxService>()
as MockMessageBoxService;
msgBox.ShowReturnValue = MessageBoxResult.Cancel;
msgBox.ShowCallCount = 0;
Assert.IsTrue(personVM.CanSave);
Assert.IsFalse(personScreen.TryToClose());
Assert.IsTrue(personVM.CanSave);
Assert.AreEqual(1, msgBox.ShowCallCount);
msgBox.ShowReturnValue = MessageBoxResult.No;
msgBox.ShowCallCount = 0;
Assert.IsTrue(personVM.CanSave);
Assert.IsTrue(personScreen.TryToClose());
Assert.IsTrue(personVM.CanSave);
Assert.AreEqual(1, msgBox.ShowCallCount);
msgBox.ShowReturnValue = MessageBoxResult.Yes;
msgBox.ShowCallCount = 0;
Assert.IsTrue(personVM.CanSave);
Assert.IsTrue(personScreen.TryToClose());
Assert.IsFalse(personVM.CanSave);
Assert.AreEqual(1, msgBox.ShowCallCount);
}
Now let’s turn our focus to how the service container is configured when the user runs the application.
Run-time
When the application is running we use a different implementation of IMessageBoxService
. This version of the service actually shows a message box and returns the result selected by the user. That class is seen below:
internal class MessageBoxService : IMessageBoxService
{
MessageBoxResult IMessageBoxService.Show(
string text,
string caption,
MessageBoxButton buttons,
MessageBoxImage image)
{
return MessageBox.Show(text, caption, buttons, image);
}
}
An instance of the MessageBoxService
class is placed into the service container when the application is first created:
public partial class App : Application
{
public App()
{
ServiceInjector.InjectServices();
}
}
public static class ServiceInjector
{
public static void InjectServices()
{
ServiceContainer.Instance.AddService<IMessageBoxService>(
new MessageBoxService());
}
}
In the demo application all of the code related to services, except for the mock service, is in the Demo.Services class library project. This allows the MessageBoxService
class to be marked internal
so that the executable cannot directly reference its type. Instead, the executable must reference IMessageBoxService
, which helps to reduce coupling and allows for Inversion of Control to work its magic.
Revision History
- April 1st, 2010 – Published article on CodeProject