Home / WPF / WPF navigation with dependency injection (DI/IOC): part 2

WPF navigation with dependency injection (DI/IOC): part 2

This article explains how to create dialogs and modal windows in a WPF application that uses dependency injection. We will use services and factories to abstract the creation of windows and MessageBoxes. We will also see how to write unit tests for MVVM navigation.

This is the second part of the article, if you are interested in the first part, you can find it here.

Part 2 summary:

  • How to show dialog/modal windows
  • How to close the dialogs
  • MessageBox and other system dialogs
  • Unit test for the navigation
  • Unit test for dialogs

Frameworks used:

  • Castle Windsor: DI container
  • MVVM light: for Messenger, ViewModelBase and RelayCommand
  • MsTest: for unit tests
  • Moq: for mocking the services

How to show dialog/modal windows

It’s common in desktop applications to show dialog windows. When we use dependency injection, we face 3 problems:

  • We don’t want to create a View from the ViewModel, because it can’t be tested. So we have to abstract the view somehow.
  • We can’t get the ViewModel directly from the container, because it can’t create the window. So we need something that creates the View for us.
  • We can’t register the View in the container, because once we close the view we cannot recreate it without resolving it.

In this case we use the Factory pattern, which consists in a class that will create the dialog for us. The factory class will be injected with the ViewModel.

Let’s use a simple dialog to illustrate how it works:

dialog sample

Let’s create the classes and the factory:

dialog classes

public interface IEditTitleDialogFactory
{
    IEditTitleDialog Create(string title);
}

public class EditTitleDialogFactory : IEditTitleDialogFactory
{
    private readonly EditTitleDialogViewModel _dialogViewModel;

    public EditTitleDialogFactory(EditTitleDialogViewModel dialogViewModel)
    {
        _dialogViewModel = dialogViewModel;
    }

    public IEditTitleDialog Create(string title)
    {
        _dialogViewModel.Title = title;
        return new EditTitleDialog(_dialogViewModel);
    }
}

We also create an interface on the View, to abstract it.

public interface IEditTitleDialog
{
    string ViewModelTitle { get; }

    bool? ShowDialog();
}

public partial class EditTitleDialog : Window, IEditTitleDialog
{
    public string ViewModelTitle { get; private set; }

    private readonly EditTitleDialogViewModel _dialogViewModel;

    public EditTitleDialog(EditTitleDialogViewModel dialogViewModel)
    {
        InitializeComponent();
        this.Owner = App.Current.MainWindow;
        this.DataContext = _dialogViewModel = dialogViewModel;
    }  
}

Then we register the Factory and DialogViewModel in the bootstrapper:

container.Register(Component.For<EditTitleDialogViewModel>());
container.Register(Component.For<IEditTitleDialogFactory>()
         .ImplementedBy<EditTitleDialogFactory>());

We want to display the dialog on the FirstViewModel, so we create a button, a command and we add the factory in the parameters of the constructor.

public FirstViewModel(DialogFactory dialogFactory)
{
    _dialogFactory = dialogFactory;
[…]
}
private void ShowDialog()
{
    var dialog = _dialogFactory.Create();
    dialog.ShowDialog();
}

How to close dialogs

When we have to close the dialog we face another problem, because the ViewModel is not aware of the view and cannot close it.

In this case the view has to be aware of the ViewModel, so we can proceed with two options:

  • We can use the button click event, access to the DataContext, invoke the command and then return the dialog result.
  • Bind to a command and create an event when the command is executed. From the view you subscribe to the “CommandExecutedEvent” and then you return the Dialog Result. Because we are subscribing to a singleton viewModel, we must unsubscribe from the event when we close the window, otherwise we will cause a memory leak.

Example of the first approach:

public partial class DialogWindow : Window
{
    private DialogViewModel _dialogViewModel;

    public DialogWindow(DialogViewModel dialogViewModel)
    {
        InitializeComponent();
        this.Owner = App.Current.MainWindow;
        this.DataContext = _dialogViewModel = dialogViewModel;
    }

    private void btnCancel_Click(object sender, RoutedEventArgs e)
    {
        _dialogViewModel.CancelCommand.Execute(null);
        this.DialogResult = false;
    }
}

Example of the second approach:

1) We need to create an “observable command” class, so we inherit from RelayCommand and we override the Executed method to fire an event when the command has been executed.

public class ObservableCommand : RelayCommand
{
    
    public event EventHandler CommandExecuted;

    public ObservableCommand(Action execute) : base(execute)
    {        
    }
    
    public ObservableCommand(Action execute, Func<bool> canExecute) 
                            : base(execute, canExecute)
    {        
    }

    public override void Execute(object parameter)
    {
        base.Execute(parameter);
        OnExecuteCommand();
    }

    private void OnExecuteCommand()
    {
        if (CommandExecuted != null)
        {
            CommandExecuted(this, EventArgs.Empty);
        }
    }
}

2) We create an ObservableCommand in DialogViewModel

public class DialogViewModel : ViewModelBase
{  
    public ObservableCommand ConfirmCommand { get; private set; }

    public DialogViewModel()
    {        
        ConfirmCommand = new ObservableCommand(Confirm);
    }
}
private void Confirm()
{
  // add code to interact with Ok pressed            
}

3) In the xaml we assign the command to the Ok button

<Button Command="{Binding ConfirmCommand}" Content="Ok" />

4) In the code behind, we subscribe to CommandExecuted event and we return the dialog result in the callback. Then we override the OnClosing method to unsubscribe from CommandExecuted.
The unsubscription is really important, because the ViewModel is a singleton and the Window will not be released by the Garbage Collector, because it holds a strong reference to the ViewModel.
This will cause a memory leak every time we open the dialog.

public partial class DialogWindow : Window
{
    private DialogViewModel _dialogViewModel;

    public DialogWindow(DialogViewModel dialogViewModel)
    {
        InitializeComponent();
        this.Owner = App.Current.MainWindow;
        this.DataContext = _dialogViewModel = dialogViewModel;
        _dialogViewModel.ConfirmCommand.CommandExecuted
                            += ConfirmCommand_CommandExecuted;
    }

    void ConfirmCommand_CommandExecuted(object sender, EventArgs e)
    {
        ViewModelTitle = _dialogViewModel.Title;
        this.DialogResult = true;
    }    

    protected override void OnClosing(CancelEventArgs e)
    {
        _dialogViewModel.ConfirmCommand.CommandExecuted
                   -= ConfirmCommand_CommandExecuted;
    }
}

MessageBox and other system dialogs

When using MVVM pattern it is common to use MessageBox and system dialogs from the ViewModel.

messagebox sample

The easiest method to use them is to call the static method, like in this example:

private void ShowSecondView()
{
    var result = MessageBox.Show("Are you sure to navigate to the second view ?", 
        "Question", MessageBoxButton.YesNo, MessageBoxImage.Question);
    if (result == MessageBoxResult.Yes)
    {
        Messenger.Default.Send(new ChangePage(typeof(SecondViewModel)));
    }
}

The problem of this approach is that it breaks the testability of the method. If you want to write a unit test that verifies that the page changes when the user clicks yes, you have to actually click on the MessageBox.

To solve this problem we abstract the MessageBox by creating a service that shows the MessageBoxs, and at runtime we inject the MessageBox, when testing we will use a Mock.

This is the service that will create the MessageBox:

public interface IMessageBoxService
{
    MessageBoxResult Show(string messageBoxText, string caption, 
	                      MessageBoxButton button, MessageBoxImage icon);
}

public class MessageBoxService
{
    public MessageBoxResult Show(string messageBoxText, string caption, 
	                             MessageBoxButton button, MessageBoxImage icon)
    {
        return MessageBox.Show(messageBoxText, caption, button, icon);
    }
}

To use it on the FirstViewModel, we have two options:

  • We can use constructor injection, but this will make our testing harder
  • We can use property injection, so our testing will be easier in a later stage

To use property injection we just create a property of IMessageBoxService in the FirstViewModel class, then we replace the call from MessageBox.Show to MessageBoxService.Show:

public IMessageBoxService MessageBoxService { get; set; }

private void ShowSecondView()
{
    var result = MessageBoxService.Show("Are you sure you want to navigate to the second view ?", 
        "Question", MessageBoxButton.YesNo, MessageBoxImage.Question);
    if (result == MessageBoxResult.Yes)
    {
        Messenger.Default.Send(new ChangePage(typeof(SecondViewModel)));
    }
}

To inject the property we just register it on Castle Windsor and it will resolve the property automatically for us. So on the bootstrapper we add:

container.Register(Component.For<IMessageBoxService>()
	     .ImplementedBy<MessageBoxService>());

Notice that MVVM frameworks usually contain all this services, so you don’t have to create them from scratch.

Testing the Navigation

An important part of development is to write unit tests for the application, to verify that the software responds correctly to the user interaction.

For this example we will use MsTests, for the simple reason that they are already installed in Visual Studio. In my daily programming I use NUnit framework.

We will use also use Moq as mocking framework: https://github.com/Moq/moq4/

To make things work properly we need also to add some references. From NuGet we install MVVM Light libraries only and Moq. Then we add a reference to our application (Add reference -> Solution -> Projects-> put a check on the application.

references

For the first test, we test that the hierarchical navigation works as expected. We verify that if we click a navigation button the view changes correspondingly.

[TestMethod]
public void TestHierarchicalNavigation()
{
    var firstViewModel = new FirstViewModel(null);
    var secondViewModel = new SecondViewModel();
    var thirdViewModel = new ThirdViewModel();
    MainWindowViewModel mainViewModel = new MainWindowViewModel(firstViewModel, secondViewModel, thirdViewModel);
    Assert.AreEqual(firstViewModel, mainViewModel.CurrentViewModel);

    mainViewModel.ShowSecondViewCommand.Execute(null);
    Assert.AreEqual(secondViewModel, mainViewModel.CurrentViewModel);

    mainViewModel.ShowThirdViewCommand.Execute(null);
    Assert.AreEqual(thirdViewModel, mainViewModel.CurrentViewModel);
}

The second test verifies that the second and the third viewmodel navigations work as expected.

[TestMethod]
public void TestSecondViewModelAndThirdViewModelNavigation()
{
    var firstViewModel = new FirstViewModel(null);
    var secondViewModel = new SecondViewModel();
    var thirdViewModel = new ThirdViewModel();
    var mainViewModel = new MainWindowViewModel(firstViewModel, 
                               secondViewModel, thirdViewModel);
    mainViewModel.CurrentViewModel = secondViewModel;

    secondViewModel.ShowThirdViewCommand.Execute(null);
    Assert.AreEqual(thirdViewModel, mainViewModel.CurrentViewModel);

    thirdViewModel.ShowFirstViewCommand.Execute(null);
    Assert.AreEqual(firstViewModel, mainViewModel.CurrentViewModel);
}	

Testing the MessageBox with Moq

For the second test we verify that a MessageBox is displayed when we click on the button “Navigate to second view”. If we click Yes on the MessageBox it should navigate, if we click No the navigation will not happen.

For this case we have to provide an implementation of the IMessageBoxService that returns No the first time, and Yes the second. We will use Moq for this.

For example, this is the code that we use to mock a MessageBox that returns No:

var mock = new Mock<IMessageBoxService>();
mock.Setup(messageBox => messageBox.Show(It.IsAny<string>(), It.IsAny<string>(),
      MessageBoxButton.YesNo, MessageBoxImage.Question))
     .Returns(MessageBoxResult.No);

And here is the complete test: first the user clicks on Navigate and clicks on No, then the user clicks on Navigate and click on Yes.

[TestMethod]
public void TestFirstViewModelNavigation()
{
    var firstViewModel = new FirstViewModel(null);
    var secondViewModel = new SecondViewModel();
    var thirdViewModel = new ThirdViewModel();
    var mainViewModel = new MainWindowViewModel(firstViewModel, 
                                secondViewModel, thirdViewModel);
    Assert.AreEqual(firstViewModel, mainViewModel.CurrentViewModel);

    // we are in FirstViewModel, 
    // we click "Navigate to second" and in messagebox we click No
    var mock = new Mock<IMessageBoxService>();
    mock.Setup(messageBox => messageBox.Show(It.IsAny<string>(), 
               It.IsAny<string>(), MessageBoxButton.YesNo, MessageBoxImage.Question))
        .Returns(MessageBoxResult.No);
    firstViewModel.MessageBoxService = mock.Object;
    firstViewModel.ShowSecondViewCommand.Execute(true);
    Assert.AreEqual(firstViewModel, mainViewModel.CurrentViewModel);

    // we are in FirstViewModel, 
    // we click "Navigate to second" and in the messagebox we click Yes
    mock = new Mock<IMessageBoxService>();
    mock.Setup(messageBox => messageBox.Show(It.IsAny<string>(),
               It.IsAny<string>(), MessageBoxButton.YesNo, MessageBoxImage.Question))
        .Returns(MessageBoxResult.Yes);
    firstViewModel.MessageBoxService = mock.Object;
    firstViewModel.ShowSecondViewCommand.Execute(true);
    Assert.AreEqual(secondViewModel, mainViewModel.CurrentViewModel);
}

Testing with windows and factories

The fourth test verifies that if we click on the “Edit title dialog” and we enter a title and we press okay, the FirstViewModel title will change.

This test is different from the previous one, because instead of a system dialog we have our own dialog that we have to mock, and we have also a factory. This means we have to create 2 mocks to test the interaction between the dialog and the viewmodel, one for the dialog and one for the factory.

So the first step is to create the mock of the dialog. We set the property ViewModelTitle and we return true with ShowDialog();

var dialogMock = new Mock<IEditTitleDialog>();
dialogMock.Setup(dialog => dialog.ViewModelTitle).Returns("Edited title");
dialogMock.Setup(dialog => dialog.ShowDialog()).Returns(true);

The second step is to mock the factory. The Create() method must return the dialog that we mocked.

var factoryMock = new Mock<IEditTitleDialogFactory>();
factoryMock.Setup(factory => factory.Create(It.IsAny<string>()))
           .Returns(dialogMock.Object);

And then we can create the viewModel with the correct factory and execute the command:

var firstViewModel = new FirstViewModel(factoryMock.Object);
firstViewModel.ShowDialogCommand.Execute(null);

Here is the full test:

[TestMethod]
public void TestFirstViewModelConfirmEditTitle()
{
    var dialogMock = new Mock<IEditTitleDialog>();
    dialogMock.Setup(dialog => dialog.ViewModelTitle).Returns("Edited title");
    dialogMock.Setup(dialog => dialog.ShowDialog()).Returns(true);

    var factoryMock = new Mock<IEditTitleDialogFactory>();
    factoryMock.Setup(factory => factory.Create(It.IsAny<string>()))
               .Returns(dialogMock.Object);

    var firstViewModel = new FirstViewModel(factoryMock.Object);
    Assert.AreEqual("Hello from First ViewModel", firstViewModel.Name);

    firstViewModel.ShowDialogCommand.Execute(null);
    Assert.AreEqual("Edited title", firstViewModel.Name);
}

The last test verifies that if we click on cancel, the title of the FirstViewModel doesn’t change.

[TestMethod]
public void TestFirstViewModelCancelEditTitle()
{
    var dialogMock = new Mock<IEditTitleDialog>();
    dialogMock.Setup(dialog => dialog.ViewModelTitle).Returns("Edited title");
    dialogMock.Setup(dialog => dialog.ShowDialog()).Returns(false);

    var factoryMock = new Mock<IEditTitleDialogFactory>();
    factoryMock.Setup(factory => factory.Create(It.IsAny<string>()))
	           .Returns(dialogMock.Object);

    var firstViewModel = new FirstViewModel(factoryMock.Object);
    Assert.AreEqual("Hello from First ViewModel", firstViewModel.Name);

    firstViewModel.ShowDialogCommand.Execute(null);
    Assert.AreEqual("Hello from First ViewModel", firstViewModel.Name);
}	

Download sample application

The sample application, including the source code, can be downloaded on my GitHub repository.

5 comments

  1. I just wanted to say, thanks so much for this. Your blog is a fantastic resource, as I’m currently working with C#, WPF, interfacing to a PLC over Modbus… And this article is so timely for me, exactly what I’ve been looking for!

    • I’m glad that you liked it. Soon I will do also a video on these 2 articles. Good luck with your project !

      • I look forward to the videos! I have a lot of learning to do before I fully understand this example. I’m simultaneously trying to learn about WPF, MVVM, DI/IOC, Unit testing…

  2. Good article, thanks!

    I’m not sure I like how each page must be passed when creating MainWindowViewModel though..

    • If you want to set the ViewModel in the ContentView and template it, you need the instance of the VM in MainWindow.

      You can write your own NavigationService, which takes ownership of that ContentView somehow, but that wasn’t the point of the article.

      Right now I prefer to use PRISM navigation, which takes another direction because it resolves ViewModels by reflection and uses strings for the navigation.
      It’s waaaay easier that this solution, you don’t need datatemplating, you have all the attached properties that you need to support regions and it has a full blown navigation stack.

Leave a Reply to mesta Cancel reply

Your email address will not be published.