Click here to Skip to main content
Click here to Skip to main content

Using a Service Locator to Work with MessageBoxes in an MVVM Application

By , 5 Apr 2010
Rate this:
Please Sign up or sign in to vote.

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:

Custom message box from BubbleBurst
 
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.

An example of dependency injection
 
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 application showing a message box when the user tries to close the window but there are unsaved changes.
 
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:

Diagram of the Person data model class
 
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.

Diagram of the PersonViewModel class and related types
 
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
{
    // This method is called once before any test executes.
    [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;
    
    // User clicks the Cancel button -- should not close or save
    msgBox.ShowReturnValue = MessageBoxResult.Cancel;
    msgBox.ShowCallCount = 0;
    Assert.IsTrue(personVM.CanSave);
    Assert.IsFalse(personScreen.TryToClose());
    Assert.IsTrue(personVM.CanSave);
    Assert.AreEqual(1, msgBox.ShowCallCount);

    // User clicks the No button -- should close but not save
    msgBox.ShowReturnValue = MessageBoxResult.No;
    msgBox.ShowCallCount = 0;
    Assert.IsTrue(personVM.CanSave);
    Assert.IsTrue(personScreen.TryToClose());
    Assert.IsTrue(personVM.CanSave);
    Assert.AreEqual(1, msgBox.ShowCallCount);

    // User clicks the Yes button -- should close and save
    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();
    }
}

// In the Demo.Services project
public static class ServiceInjector
{
    // Loads service objects into the ServiceContainer on startup.
    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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Josh Smith
Software Developer (Senior) Cynergy Systems
United States United States
Josh creates software, for iOS and Windows.
 
He works at Cynergy Systems as a Senior Experience Developer.
 
Read his iOS Programming for .NET Developers[^] book to learn how to write iPhone and iPad apps by leveraging your existing .NET skills.
 
Use his Master WPF[^] app on your iPhone to sharpen your WPF skills on the go.
 
Check out his Advanced MVVM[^] book.
 
Visit his WPF blog[^] or stop by his iOS blog[^].
Follow on   Twitter

Comments and Discussions

 
SuggestionIs there not a memory leak in Service Container ? PinmemberVasudevan Kannan17-Jul-12 20:27 
GeneralRe: Is there not a memory leak in Service Container ? Pinmemberbombersa19-Sep-12 9:25 
GeneralMy vote of 5 PinmemberShahin Khorshidnia12-Feb-12 18:49 
QuestionA much simpler approach PinmemberAdrian Alexander18-Dec-11 19:04 
QuestionMy vote of 5... PinmemberGerard Castelló Viader26-Sep-11 23:14 
GeneralMy vote of 5 PinmemberMember 404134111-Mar-11 17:12 
GeneralMy vote of 5 PinmemberMahmudul Haque Azad22-Feb-11 5:25 
GeneralLike having services opening dialogs Pinmemberdisore5-Apr-10 23:49 
GeneralRe: Like having services opening dialogs PinmvpJosh Smith6-Apr-10 5:25 
QuestionHow does Service Locator differ from an IoC container ? [modified] Pinmembertomlev4-Apr-10 8:38 
AnswerRe: How does Service Locator differ from an IoC container ? PinmvpJosh Smith4-Apr-10 18:26 
AnswerRe: How does Service Locator differ from an IoC container ? PinmemberWilliam E. Kempf5-Apr-10 2:24 
QuestionRe: How does Service Locator differ from an IoC container ? PinprotectorHeath Stewart30-Sep-10 8:45 
AnswerRe: How does Service Locator differ from an IoC container ? PinmemberWilliam E. Kempf30-Sep-10 8:54 
AnswerRe: How does Service Locator differ from an IoC container ? Pinmembertomlev5-Apr-10 5:13 
QuestionWhy not inject the message box service directly? Pinmemberwcoenen2-Apr-10 6:12 
AnswerRe: Why not inject the message box service directly? PinmvpJosh Smith2-Apr-10 7:04 
Thanks for the feedback.
 
wcoenen wrote:
There are good reasons to consider service locator an anti-pattern

 
Sure, any pattern misused can be a problem. I agree 100%.
 
wcoenen wrote:
Why not just inject the message box service directly through a constructor argument?

 
Constructor injection is definitely a valid option, but I don't see it as solving a problem. That's just passing off the job of locating an object's dependencies to its creator. I'm not a zealot about these things. Whatever fits one's needs and preferences is what one should use.
:josh:
Advanced MVVM[^]
Advance your MVVM skills

GeneralRe: Why not inject the message box service directly? PinmemberEvan Lang2-Apr-10 7:27 
GeneralRe: Why not inject the message box service directly? PinmvpJosh Smith2-Apr-10 7:31 
GeneralRe: Why not inject the message box service directly? Pinmemberwcoenen2-Apr-10 16:36 
GeneralRe: Why not inject the message box service directly? PinmemberWilliam E. Kempf5-Apr-10 2:22 
GeneralRe: Why not inject the message box service directly? Pinmemberwcoenen5-Apr-10 5:08 
AnswerRe: Why not inject the message box service directly? PinmemberJonahSimpson2-Apr-10 10:47 
GeneralRe: Why not inject the message box service directly? PinmvpJosh Smith2-Apr-10 11:10 
GeneralWell written and straight to the point. My vote of 5. PinmvpNishant Sivakumar2-Apr-10 2:47 
GeneralRe: Well written and straight to the point. My vote of 5. PinmemberWilliam E. Kempf2-Apr-10 3:15 
GeneralRe: Well written and straight to the point. My vote of 5. PinmvpNishant Sivakumar2-Apr-10 3:20 
GeneralRe: Well written and straight to the point. My vote of 5. PinmvpSacha Barber2-Apr-10 6:49 
GeneralRe: Well written and straight to the point. My vote of 5. PinmemberWilliam E. Kempf2-Apr-10 12:22 
GeneralRe: Well written and straight to the point. My vote of 5. PinmvpNishant Sivakumar2-Apr-10 12:26 
GeneralRe: Well written and straight to the point. My vote of 5. PinmvpJosh Smith2-Apr-10 3:51 
GeneralRe: Well written and straight to the point. My vote of 5. PinmvpSacha Barber2-Apr-10 6:47 
GeneralNice job agent Smith PinmvpSacha Barber1-Apr-10 21:13 
GeneralRe: Nice job agent Smith PinmvpNishant Sivakumar2-Apr-10 3:02 
GeneralRe: Nice job agent Smith PinmvpSacha Barber2-Apr-10 6:45 
GeneralRe: Nice job agent Smith PinmvpJosh Smith2-Apr-10 3:53 
GeneralRe: Nice job agent Smith PinmvpSacha Barber2-Apr-10 6:45 
GeneralA very nice and easy to understand article on a very common task PinmemberKarl Shifflett1-Apr-10 15:26 
GeneralRe: A very nice and easy to understand article on a very common task PinmvpJosh Smith1-Apr-10 15:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140415.2 | Last Updated 5 Apr 2010
Article Copyright 2010 by Josh Smith
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid