[C# Design Patterns] MVVM (Model-View-ViewModel)

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.

 

View (UI) file are the UI project. Models and ViewModels are in a Class Library project

 

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

 

AccountCreationView.xaml.cs

 

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”.

To add the Engine project, Add a Reference to the Solution and check the Engine project checkbox

 

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

 

 

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

 

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.

To add the Engine project, Add a Reference to the Solution and check the Engine project checkbox

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

 

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

 

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

 

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://scottlilly.com/GHCSharpDesignPatterns

14 thoughts on “[C# Design Patterns] MVVM (Model-View-ViewModel)

  1. These step-by-step explanations are easy to follow. I’m a complete beginner at learning C. Other tutorials on MVVM aren’t as clear as to what the point is. I want to know maybe another example of implementing MVVM. Besides running Ui tests, can you name a few other scenarios? I’m just trying to understand and map out different cases of use. Thanks for all the support you provide here!

    1. You’re welcome!

      The other big benefit of MVVM is that it is easy to create another user interface for your program.

      For example, you could create a solution with a Class Library project (for the ViewModels and Models), and a Desktop WPF project (for the Views). Then, you can add an ASP.NET project to the solution, have it reference the Class Library project, and it can use all the existing ViewModels and Models in that project. You will only need to create the new Views, connect them to the ViewModels, and your project is done!

      Plus, if you change the logic in a Model, the change would happen for both the WPF program and the ASP.NET program – because they are both referencing the same Class Library project. So, you do not need to remember to make the change in multiple places (which you would need to do, if the logic was in the UI projects).

      1. I am brand new on .NET Framework in general and on MVVM in particular.
        I have downloaded your excellent lesson on MVVM which helped me to understand the benefits of this technique.
        I have caught the functioning of the Engine and TestEngine dll’s and of the WorkbenchWPF
        project.
        There are two things I could not understand:
        – The role of the Workbench project.
        When called, it displays a Windows form with a button name “Text” in it.
        When clicked, a popup windows shows up with a “Got here” message in it.
        Is it supposed to do something else?

        – In the Engine dll, the code for many other builders is present.
        How is it possible to execute the code for other builders than MVVM?

        Sorry for those trivial questions, but I am on the learning curve.

        1. Hi Pierre,

          The Workbench and WorkbenchWPF projects are small programs to demonstrate the code for the MVVM design pattern. The other design patterns are tested with unit tests in the TestEngine project. However, the MVVM design pattern needs a View. So, I needed to create projects with a UI (the view). The “Got here” message is the only thing it is supposed to do – so we can confirm the code executed correctly.

          To run the code for the other design patterns, you can run their unit tests in the TestEngine project. The folder name in the TestEngine class is the same name as the design pattern’s folder in the Engine project. You can see more about running unit tests here: https://msdn.microsoft.com/en-us/library/ms182532.aspx

          Please tell me if you have more questions.

  2. Nice post. I like reading up to date information. Here’s a tip. You have the ?. for your NotifyPropertyChanged, but apparently you haven’t met the attribute CallerMemberName. No need to do nameof(Password). Calling the method like below, gives you the calling NotifyPropertyChanged() with no arguments, gives you the member name automatically.

    private void NotifyPropertyChanged([CallerMemberName] string propertyName = “”)
    {
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    1. Hi Chuck,

      Thanks for mentioning that method. I normally don’t use attributes in the example code here – in order to have one less thing to explain. However, the CallerMemberName is a cleaner way to handle getting the property name in a property change notification function.

  3. Hi Lilly
    This very neat & clean example for beginner as well as experienced programmers. Please post some examples with Prism also.
    A great! Article.
    Regards
    Hari prakash

  4. As someone getting back into programming after about a decade now, this was an excellent refresher on some old concepts (was taught more MVC, but it wasn’t too difficult to reapply to MVVM) I learned in school.

    I did have one question though!

    It might still be part of the rust, but part of me is wondering if the Model should be as “barebones” as possible. I was talking with a colleague and he suggested that he’d probably consider that the Model not include any of the event handling, but another said they were happy to see the solution I was working on included an implementation of INotifyPropertyChanged.

    So clearly some philosophical split (although maybe not a big deal in practice) among some coworkers. Curious your thoughts as part of me could easily see the ViewModel being the place where the property change events are fired. Though as I type this, I wonder if maybe it’d be better to do it this way because it could be possible for there to be different ViewModels that look at the same Model?

    In any case, this was excellent and even if you don’t have to to answer, it was a big help in getting me ramped up without pestering my coworkers with too many questions!

    Cheers.

    1. Thanks Allan!

      This is definitely a philosophical split.

      I mostly write WPF desktop apps. In those, the View databinds its controls to properties in the ViewModels and/or the Models. So, I almost must have propertychanged notifications in both places. Especially because I like to use derived (read-only) properties when possible.

      For example, when my game Player object’s ExperiencePoints property changes, I need to let the UI know its derived Level property also needs to be refreshed. The simplest (and most-appropriate, IMO) way to deal with this is to have the Player object raise the propertychanged event for Level.

      I could force everything to work through the ViewModels. But, I think that would add a lot of code, with no practical benefit.

      However, if you are trying to write your C# code in a “functional” style (like F#, Haskell, or OCaml), you might treat your Models as immutable objects that can only have their property values set at instantiation. When you need to change an object’s property, you actually create a new object with the modified values.

      In this case, you obviously wouldn’t need to send notifications from the models – since no properties are ever changed. Instead, the ViewModel would notify the View that it’s holding a new Model object.

      Of course, my ultimate answer is, “Do it however the person who signs the checks wants it done. Then, go home and do it the ‘right’ way for your personal projects.”

  5. Hi Scott,
    Thank you very much for the time you spend explaining things so well and helping others to learn better programming practices.
    I am an experienced programmer, but very new to C# and WPF. I’m really glad I’ve found your tutorials.
    By the way, I have following problem with the Pattern Version:
    If I click on “Check Password” before entering anything in the text box above, I get a System.NullReferenceException.
    However if I type something into the text box, everything works fine even if I empty the text field before clicking the button.
    Any ideas what I’m doing wrong? I think, I didn’t change your code.

    Best regards,
    Daniel
    Switzerland

    =================================================================
    System.NullReferenceException
    HResult=0x80004003
    Message=Object reference not set to an instance of an object.
    Source=Engine
    StackTrace:
    at Engine.MVVMPattern.PatternVersion.Models.AccountModel.ValidatePassword() in C:\Users\dakr\source\repos\CSharpDesignPattern\Engine\MVVMPattern\PatternVersion\Models\AccountModel.cs:line 44
    at Engine.MVVMPattern.PatternVersion.ViewModels.AccountCreationViewModel.ValidatePassword() in C:\Users\dakr\source\repos\CSharpDesignPattern\Engine\MVVMPattern\PatternVersion\ViewModels\AccountCreationViewModel.cs:line 16
    at WorkbenchWPF.MVVMPattern.PatternVersion.AccountCreationView.OnClick_ValidatePassword(Object sender, RoutedEventArgs e) in C:\Users\dakr\source\repos\CSharpDesignPattern\WorkbenchWPF\MVVMPattern\PatternVersion\AccountCreationView.xaml.cs:line 19
    at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
    at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
    at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
    at System.Windows.UIElement.RaiseEvent(RoutedEventArgs e)
    at System.Windows.Controls.Primitives.ButtonBase.OnClick()
    at System.Windows.Controls.Button.OnClick()
    at System.Windows.Controls.Primitives.ButtonBase.OnMouseLeftButtonUp(MouseButtonEventArgs e)
    at System.Windows.UIElement.OnMouseLeftButtonUpThunk(Object sender, MouseButtonEventArgs e)
    at System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
    at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
    at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
    at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
    at System.Windows.UIElement.ReRaiseEventAs(DependencyObject sender, RoutedEventArgs args, RoutedEvent newEvent)
    at System.Windows.UIElement.OnMouseUpThunk(Object sender, MouseButtonEventArgs e)
    at System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
    at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
    at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
    at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
    at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
    at System.Windows.UIElement.RaiseTrustedEvent(RoutedEventArgs args)
    at System.Windows.UIElement.RaiseEvent(RoutedEventArgs args, Boolean trusted)
    at System.Windows.Input.InputManager.ProcessStagingArea()
    at System.Windows.Input.InputManager.ProcessInput(InputEventArgs input)
    at System.Windows.Input.InputProviderSite.ReportInput(InputReport inputReport)
    at System.Windows.Interop.HwndMouseInputProvider.ReportInput(IntPtr hwnd, InputMode mode, Int32 timestamp, RawMouseActions actions, Int32 x, Int32 y, Int32 wheel)
    at System.Windows.Interop.HwndMouseInputProvider.FilterMessage(IntPtr hwnd, WindowMessage msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
    at System.Windows.Interop.HwndSource.InputFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
    at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
    at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
    at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
    at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
    at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
    at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
    at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
    at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
    at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
    at System.Windows.Window.ShowHelper(Object booleanBox)
    at System.Windows.Window.Show()
    at System.Windows.Window.ShowDialog()
    at WorkbenchWPF.MainWindow.OnClick_MVVMPattern_PatternVersion(Object sender, RoutedEventArgs e) in C:\Users\dakr\source\repos\CSharpDesignPattern\WorkbenchWPF\MainWindow.xaml.cs:line 23
    at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
    at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
    at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
    at System.Windows.UIElement.RaiseEvent(RoutedEventArgs e)
    at System.Windows.Controls.Primitives.ButtonBase.OnClick()
    at System.Windows.Controls.Button.OnClick()
    at System.Windows.Controls.Primitives.ButtonBase.OnMouseLeftButtonUp(MouseButtonEventArgs e)
    at System.Windows.UIElement.OnMouseLeftButtonUpThunk(Object sender, MouseButtonEventArgs e)
    at System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
    at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
    at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
    at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
    at System.Windows.UIElement.ReRaiseEventAs(DependencyObject sender, RoutedEventArgs args, RoutedEvent newEvent)
    at System.Windows.UIElement.OnMouseUpThunk(Object sender, MouseButtonEventArgs e)
    at System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
    at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
    at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
    at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
    at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
    at System.Windows.UIElement.RaiseTrustedEvent(RoutedEventArgs args)
    at System.Windows.UIElement.RaiseEvent(RoutedEventArgs args, Boolean trusted)
    at System.Windows.Input.InputManager.ProcessStagingArea()
    at System.Windows.Input.InputManager.ProcessInput(InputEventArgs input)
    at System.Windows.Input.InputProviderSite.ReportInput(InputReport inputReport)
    at System.Windows.Interop.HwndMouseInputProvider.ReportInput(IntPtr hwnd, InputMode mode, Int32 timestamp, RawMouseActions actions, Int32 x, Int32 y, Int32 wheel)
    at System.Windows.Interop.HwndMouseInputProvider.FilterMessage(IntPtr hwnd, WindowMessage msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
    at System.Windows.Interop.HwndSource.InputFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
    at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
    at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
    at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
    at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
    at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
    at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
    at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
    at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
    at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
    at System.Windows.Application.RunDispatcher(Object ignore)
    at System.Windows.Application.RunInternal(Window window)
    at System.Windows.Application.Run(Window window)
    at System.Windows.Application.Run()
    at WorkbenchWPF.App.Main()

    1. You’re welcome Daniel,

      The error you’re seeing is a strange one that I’ve seen a few other times. The property’s datatype is “string”, so its default value should be an empty string (not null). But, sometimes, when a string property is bound to an input control in WPF, it has a null value. That should be impossible, unless the datatype is “string?” (a nullable string)”. I haven’t figured out how that happens. I think I usually fix that by explicitly setting the string property’s value to an empty string (in the class constructor or by doing “public string MyProperty {get; set;} = “”;). But, I would like to know why this sometimes happens.

Leave a Reply

Your email address will not be published. Required fields are marked *