This lesson will demonstrate how to use the MVVM (Model-View-ViewModel) design pattern.
This will cover the basic way of using it. There are some more advanced techniques you can add – like using DelegateCommand and RelayCommand. But, I won’t cover them in this demonstration.
The MVVM pattern is similar to the MVP (Model-View-Presenter) and MVC (Model-View-Controller) patterns.
Video version here:
What is MVVM?
MVVM is a way to organize code. It’s one way to separate your user interface from your logic.
“Views” will only hold the user interface code – displaying the screen (textboxes, labels, buttons, etc.) and accepting user input.
For example, in a Windows Form project, this would usually be a Form, or a Control. In a WPF project, this would be the Window class – the XAML and xaml.cs files.
“Models” are the business classes.
These are the “things” in your program. For example, in an order entry program, your model classes might be: Customer, Order, InventoryItem. These classes will contain the logic they need, to perform your business functions.
“ViewModels” are used to communicate between the Views and the Models.
They usually don’t hold much logic. They often hold the Models that need to be used in the View. When the user interacts with the UI, the ViewModel will know what action needs to be performed, and will ask the Model, or Models, to perform the requested action.
NOTE: In this example, we will start with a very simple code example. When we change it to use MVVM, we will have another project, and more classes. It will look like the code is more complex.
However, after creating the MVVM version, I will show you some powerful benefits from separating the code this way.
Non-pattern version
In this example, I’ll use a screen that will test if a password is secure. It will check if the password is the correct length (between 8 and 20 characters), contains at least one upper-case letter, at least one lower-case letter, at least one number, and at least one symbol.
NOTE: If this was code for a real program, we would probably want to display all validation messages, if a password failed more than one check. However, for this demonstration, I’m keeping the code simple.
Without using MVVM, you might write the code like below. All the logic to check the password is in the button “Click” event, in the xaml.cs file.
AccountCreationView.xaml
<Window x:Class="WorkbenchWPF.MVVMPattern.NonPatternVersion.AccountCreationView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" Title="MVVM: Non-Pattern Version" Height="300" Width="400"> <Grid HorizontalAlignment="Center" VerticalAlignment="Center"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="300"/> </Grid.ColumnDefinitions> <TextBlock Grid.Row="0" Name="lblErrorMessage" Foreground="Red" TextWrapping="Wrap" Width="200" HorizontalAlignment="Center" Padding="5" Margin="5"/> <TextBox Grid.Row="1" Name="txtPassword" Width="200" HorizontalAlignment="Center" Padding="5" Margin="5"/> <Button Grid.Row="2" Content="Check Password" Width="200" HorizontalAlignment="Center" Click="OnClick_ValidatePassword" Padding="5" Margin="5"/> </Grid> </Window>
AccountCreationView.xaml.cs
using System.Linq; using System.Windows; namespace WorkbenchWPF.MVVMPattern.NonPatternVersion { public partial class AccountCreationView : Window { public AccountCreationView() { InitializeComponent(); } private void OnClick_ValidatePassword(object sender, RoutedEventArgs e) { if(txtPassword.Text.Trim().Length < 8) { lblErrorMessage.Text = "Password must be at least eight characters long"; } else if(txtPassword.Text.Trim().Length > 20) { lblErrorMessage.Text = "Password cannot be more than twenty characters long"; } else if(!txtPassword.Text.Any(char.IsUpper)) { lblErrorMessage.Text = "Password must contain at least one upper-case character"; } else if(!txtPassword.Text.Any(char.IsLower)) { lblErrorMessage.Text = "Password must contain at least one lower-case character"; } else if(!txtPassword.Text.Any(char.IsNumber)) { lblErrorMessage.Text = "Password must contain at least one number"; } else { lblErrorMessage.Text = "Password is secure"; } } } }
There are two problems with writing code this way.
First, the only way to test the logic is to manually run the program, and type in values for each scenario – unless you have some good UI testing tools.
By using MVVM, the “business logic” is moved out of the UI project, and into a class library – which can be used by different front-ends (web, desktop, console, etc.), without needing to copy it. You can also (usually) test it much more easily.
Second, if you want to write an Internet version of your program, you’ll need to copy all the code from the xaml.cs file into the aspx.cs file your new project. If you ever make a change to the program, you need to make the change in two places – if you remember to do that.
Pattern Version
Note: I use “View”, “Model” and “ViewModel” in the names of these folders, and classes. That is not required for MVVM. I just do it to make this demonstration code easier to understand.
Step 1: Create a class library project
For this example, I’ll name the project “Engine”. This project will hold the Models and ViewModels. It is a class library because we will only have logic in it, no UI code.
Create two new folders in the Engine projects, named “Models” and “ViewModels”.
Step 2: Create the Model
In the Model folder, create a class named “AccountModel.cs”. This represents a user account, which has the password we want to check for complexity.
AccountModel.cs
using System.ComponentModel; using System.Linq; namespace Engine.MVVMPattern.PatternVersion.Models { public class AccountModel : INotifyPropertyChanged { private string _name; private string _password; private string _validationMessage; public string Name { get { return _name; } set { _name = value; } } public string Password { get { return _password; } set { _password = value; OnPropertyChanged(nameof(Password)); } } public string ValidationMessage { get { return _validationMessage; } set { _validationMessage = value; OnPropertyChanged(nameof(ValidationMessage)); } } public event PropertyChangedEventHandler PropertyChanged; public void ValidatePassword() { if(Password.Trim().Length < 8) { ValidationMessage = "Password must be at least eight characters long"; } else if(Password.Trim().Length > 20) { ValidationMessage = "Password cannot be more than twenty characters long"; } else if(!Password.Any(char.IsUpper)) { ValidationMessage = "Password must contain at least one upper-case character"; } else if(!Password.Any(char.IsLower)) { ValidationMessage = "Password must contain at least one lower-case character"; } else if(!Password.Any(char.IsNumber)) { ValidationMessage = "Password must contain at least one number"; } else { ValidationMessage = "Password is secure"; } } protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }
This class has properties to hold the Password and ValidationMessage (the result from the password complexity check). It also has an unused “Name” property, so it looks like a more realistic class for a user account.
Account implements INotifyPropertyChanged. Models and ViewModels usually do this, so they can notify the UI (the View) that values have changed, and the UI needs to be refreshed.
All the logic to validate the password complexity is now in the ValidatePassword function. When it is called, it checks the Password property, and sets the ValidationMessage property with the result of the complexity validation.
Step 3: Create the ViewModel
In the ViewModels folder, create a class named AccountCreationViewModel.cs. This is the ViewModel we will use for our account creation screen. There is often one ViewModel per screen – although, there can be more, in larger programs.
AccountCreationViewModel.cs
using Engine.MVVMPattern.PatternVersion.Models; namespace Engine.MVVMPattern.PatternVersion.ViewModels { public class AccountCreationViewModel { public AccountModel NewAccount { get; set; } public AccountCreationViewModel() { NewAccount = new AccountModel(); } public void ValidatePassword() { NewAccount.ValidatePassword(); } } }
When the ViewModel is instantiated, it instantiates a new AccountModel object, and sets that object to the ViewModel’s NewAccount property. The ValidatePassword function only has one line – to call the ValidatePassword function on the Model object.
This is common for ViewModels. They are very “slim”. There is very little logic in them – in this example, there is no logic. It only passes requests to do something to the functions in the Models.
Step 4: Give the Views access to the Models and ViewModels
The View (AccountCreationView.xaml) is in a WPF project. The Models and ViewModels are in a class library project – Engine. For the View to be able to use the Models and ViewModels, it needs a reference to the Engine project.
In the WPF project, right-click on “References” (in the Solution Explorer).
Select “Add Reference…”
On the left side of the pop-up screen, select “Projects” -> “Solution”. This will display all the projects in the current solution.
Check the box next to “Engine”, and click the “OK” button.
This lets the Views in the WPF project use the Models and ViewModels in the Engine project.
Step 5: Create the View
The code for AccountCreationView is similar to the original code, with only a few changes.
On lines 6 and 7, I added two lines to let the XAML editor know we are using the AccountCreationViewModel, from the Engine project. This is not required. However, it does let you use Intellisense when binding WPF controls to properties on the ViewModel.
On lines 23 and 30, the TextBox controls now use databinding to connect their values with the properties on the ViewModel and Model. This way, AccountCreationView.xaml.cs does not need to pass values to the ViewModel/Model, or retrieve values from the ViewModel/Model.
AccountCreationView.xaml
<Window x:Class="WorkbenchWPF.MVVMPattern.PatternVersion.AccountCreationView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:viewModels="clr-namespace:Engine.MVVMPattern.PatternVersion.ViewModels;assembly=Engine" d:DataContext="{d:DesignInstance viewModels:AccountCreationViewModel}" mc:Ignorable="d" Title="MVVM: Pattern Version" Height="300" Width="400"> <Grid HorizontalAlignment="Center" VerticalAlignment="Center"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="300"/> </Grid.ColumnDefinitions> <TextBlock Grid.Row="0" Text="{Binding NewAccount.ValidationMessage}" Foreground="Red" TextWrapping="Wrap" Width="200" HorizontalAlignment="Center" Padding="5" Margin="5"/> <TextBox Grid.Row="1" Text="{Binding NewAccount.Password}" Width="200" HorizontalAlignment="Center" Padding="5" Margin="5"/> <Button Grid.Row="2" Content="Check Password" Click="OnClick_ValidatePassword" Width="200" HorizontalAlignment="Center" Padding="5" Margin="5"/> </Grid> </Window>
Next, we eliminate most of the code from AccountCreationView.xaml.cs.
All it does it instantiate a private variable, to hold the ViewModel, and set the DataContext of the Window to the ViewModel variable.
The button click handler doesn’t have any logic in it. It only asks the ViewModel to perform an action – run its ValidatePassword function.
AccountCreationView.xaml.cs
using System.Windows; using Engine.MVVMPattern.PatternVersion.ViewModels; namespace WorkbenchWPF.MVVMPattern.PatternVersion { public partial class AccountCreationView : Window { private readonly AccountCreationViewModel _viewModel = new AccountCreationViewModel(); public AccountCreationView() { InitializeComponent(); DataContext = _viewModel; } private void OnClick_ValidatePassword(object sender, RoutedEventArgs e) { _viewModel.ValidatePassword(); } } }
How the MVVM version works
Now, when the user runs the program, AccountCreationView will instantiate an AccountCreationViewModel object. When the AccountCreationViewModel is instantiated, it will instantiate an AccountModel object, and set that object to its NewAccount property.
AccountCreationView.xaml will bind its TextBox controls to the properties on the Model. When the user types a value into the password TextBox, that value will automatically be set in the Account object’s Password property – because of the databinding.
When the user clicks the “Check Password” button, the UI will run OnClick_ValidatePassword, in AccountCreationView.xaml.cs, which will call ValidatePassword in the ViewModel, which will call ValidatePassword in the Account object.
ValidatePassword will run its checks and set the ValidationMessage property – which will raise its notification message, which will be recognized by AccountCreationView, which will cause the view to update the TextBox with the new ValidationMessage.
So… how is this a better way to write a program? It looks much more complex than it did before.
Improvements with MVVM
Before, when the password checking logic was in the UI, we would have needed to run the program and manually enter six different passwords, to test all six possible results of the password-checking function.
If this was a real program, we would probably have thousands, or tens of thousands, of possible values (and combinations of values) to enter. If we did that manually, it could take days – and we would probably make mistakes.
Now, with the code separated with MVVM, we can write automated tests – which can check all the possible values in minutes.
I added a Test Project to the solution, named “TestEngine”. Then, I added a reference in that project, to the Engine project, so the test project can use our ViewModels and Models.
Now, I can write tests that pretend to be the UI, by setting values on the ViewModel and Model properties, and calling functions on the ViewModel.
Here is the code for a unit test class. It has six unit tests – one for each possible result in the password-checking function.
TestMVVMPattern.cs
using Engine.MVVMPattern.PatternVersion.ViewModels; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace TestEngine.MVVMPattern { [TestClass] public class TestMVVMPattern { [TestMethod] public void Test_PasswordIsTooShort() { AccountCreationViewModel viewModel = new AccountCreationViewModel(); viewModel.NewAccount.Password = "asd"; viewModel.ValidatePassword(); Assert.AreEqual("Password must be at least eight characters long", viewModel.NewAccount.ValidationMessage); } [TestMethod] public void Test_PasswordIsTooLong() { AccountCreationViewModel viewModel = new AccountCreationViewModel(); viewModel.NewAccount.Password = "asdasdasdasdasdasdasd"; viewModel.ValidatePassword(); Assert.AreEqual("Password cannot be more than twenty characters long", viewModel.NewAccount.ValidationMessage); } [TestMethod] public void Test_PasswordMustHaveAnUpperCaseCharacter() { AccountCreationViewModel viewModel = new AccountCreationViewModel(); viewModel.NewAccount.Password = "asdasdasd"; viewModel.ValidatePassword(); Assert.AreEqual("Password must contain at least one upper-case character", viewModel.NewAccount.ValidationMessage); } [TestMethod] public void Test_PasswordMustHaveALowerCaseCharacter() { AccountCreationViewModel viewModel = new AccountCreationViewModel(); viewModel.NewAccount.Password = "ASDASDASD"; viewModel.ValidatePassword(); Assert.AreEqual("Password must contain at least one lower-case character", viewModel.NewAccount.ValidationMessage); } [TestMethod] public void Test_PasswordMustHaveANumber() { AccountCreationViewModel viewModel = new AccountCreationViewModel(); viewModel.NewAccount.Password = "asdasdasdA"; viewModel.ValidatePassword(); Assert.AreEqual("Password must contain at least one number", viewModel.NewAccount.ValidationMessage); } [TestMethod] public void Test_PasswordIsSecure() { AccountCreationViewModel viewModel = new AccountCreationViewModel(); viewModel.NewAccount.Password = "asdasdasdA1"; viewModel.ValidatePassword(); Assert.AreEqual("Password is secure", viewModel.NewAccount.ValidationMessage); } } }
Each unit test instantiates an AccountCreationViewModel object, sets the value of its NewAccount.Password (acting like the View), and calls the ValidatePassword function (acting like the OnClick_ValidatePassword function in the View). Then, the unit test checks that the ValidationMessage is what it expects.
So, if we ever make changes to this code, we can run the unit tests and know (within a few seconds, or minutes) if the change broke anything.
In a large program, this is extremely powerful. I work with some programs that have thousands of automated unit tests. If a change breaks something, we know about it quickly, and can fix it quickly.
We can also easily create a different UI for the program. If we wanted an Internet version of the program, we would only need to create the Views (as web pages), and have them use our existing ViewModels and Models.
Summary
For this small example, MVVM is more work, for little benefit. However, it is a great investment of your time for larger programs.
I’ve been using MVVM – or similar patterns of MVP (Model-View-Presenter) or MVC (Model-View-Controller) – for years, and would never go back to having logic in the UI.
Source Code
The source code from these design patterns demonstrations is available at: https://www.scottlilly.com/GHCSharpDesignPatterns
14 Comments