Click here to Skip to main content
15,885,546 members
Articles / Desktop Programming / WPF

Bare Metal MVVM - Where the Code Meets the Road - Part 1

Rate me:
Please Sign up or sign in to vote.
4.98/5 (34 votes)
29 Dec 2016CPOL9 min read 30.5K   260   46   27
This series of articles covers MVVM from the absolute beginning; no frameworks and no helpers, we're going to learn MVVM from the nuts and bolts.

Introduction

There have been many, many articles about MVVM so you may wonder why I am writing more articles on it. The simple reason is, there's a lot of misinformation about MVVM out there and I hope that this series of articles helps to dispel some of the myths about what is a really, really straightforward pattern.

Now, you are probably aware that MVVM started off life as a pattern for WPF applications, and that it has spread out from its humble beginnings into other languages, frameworks and platforms. However, because these different implementations all rely on features that aren't visibly exposed to us, the developer, we tend to miss out on just what it is that makes MVVM such a useful tool in our development toolbox, and what features we need to start off with MVVM. So, for this first article, we're going to ditch all those fancy platforms, and build an MVVM application from the ground up as a Console application. Yes, that's right, we're going to recreate some of what goes on behind the scenes to show some of the "magic" that makes MVVM possible.

By the end of this article, we won't have covered all that we need to know about MVVM, but we will have covered the basics of binding operations and the use of INotifyPropertyChanged. Oh, and while we're at it, we'll have talked about a couple of the myths behind MVVM too.

Series

Development Environment

I have developed this application in VS 2015 and made liberal use of C#6 features. If you don't have VS2015 installed, I hope you will at least be able to follow along with the concepts as I will explain the entire codebase here.

The Models

Our application is going to be a simple one. The user will type in a name and, if it isn't an empty string, our View will display details about the update. To that end, we're going to have two models that we work with. The first model will be a simple one that contains a string for the name that the user will type in. This is about as basic as a data model can get.

C#
public class PersonModel
{
  private string name;

  public string Name
  {
    get { return name; }
    set
    {
      if (name == value)
      {
        return;
      }
      name = value;
    }
  }
}

We see, from that implementation, that there's nothing preventing the user from entering an empty string, so we're going to need some form of validation to make sure that the user can't set the string incorrectly. First of all, let's see what our validator looks like:

C#
public class PersonModelValidator
{
  public bool IsValid(string name)
  {
    if (!string.IsNullOrWhiteSpace(name))
    {
      return true;
    }
    Console.WriteLine("Name cannot be an empty string.");
    return false;
  }
}

Again, this validator is a simple piece of code but the question we have to answer right now is, in MVVM, where does this validation logic sit? We have chosen to make this a separate class, so we need an appropriate place to put this. We could always have put the validator inside the data model but there are reasons why we don't want to do this.

  1. What if this model was generated out of an ORM? Having a single point of validation mixed in here could well be incorrect.
  2. We would be mixing concerns here. This class is a model - it's not really its responsibility to define what is, or is not, an acceptable value. That's generally going to be business logic, so it would be better to keep it as external validation and have that check the "fitness" of the value.

We have decided, then, that we want our validation applied as an external factor and this has to sit somewhere. The logical place for this to sit is in the Model part of MVVM. Yes, that's right, we're going to put it in the Model. The reason we do this is because we are about to bust one of the myths that has arisen around MVVM.

MYTH BUSTER #1. In MVVM, the Model DOES NOT mean that it is just for data models.

Despite what we might have thought, MVVM is an architectural pattern, not a presentation pattern. In other words, it is intended to span the whole breadth of the application from the UI all the way through the many different layers we might have developed, be they cloud services, data access layers, business layers and even, other patterns. This is often one of the hardest things for people new to MVVM to comprehend, where does all the other logic go in MVVM? Very often, we see people trying to shoehorn layers into the ViewModel because there is this myth that only data entities can exist in the Model layer. This is not true, and we should wipe it from our minds. The model part is everything that isn't View or ViewModel. I know that seems a bit daft, at the moment, because we haven't covered what the View or ViewModel is just yet, but please bear with me, we will take a look at these layers a bit later on and things should start to make more sense.

The View

For our first iteration of the view, we're going to keep it simple and let the user type in only once. If they actually type something in, we should see a message stating that they changed the Name of the person to whatever they typed in. So, let's see what the view looks like:

C#
public class ProgramView : Framework
{
  private readonly PersonViewModel viewModel = new PersonViewModel();
  public ProgramView()
  {
    DataContext = viewModel;
    SetBinding("Name");
  }

  public void Input()
  {
    string input = Console.ReadLine();
    viewModel.Name = input;
  }
}

Now this needs some explaining, so let's start with the basics of this. Our view inherits from a class that we are calling Framework. The Framework class hides a lot of the plumbing that we are going to need. Before we continue dissecting the view, we should pause to investigate what this plumbing actually is.

C#
public abstract class Framework
{
  private readonly Dictionary<string, Binding> bindings = new Dictionary<string, Binding>();
  public object DataContext { get; set; }

  protected void SetBinding(string property)
  {
    Binding binding = new Binding(property)
    {
      Source = DataContext
    };
    binding.Parse();
    bindings.Add(property, binding);
  }
}

There are, effectively, two parts to this. The first is that we must set something as the context that we are going to bind our view to, in order to receive inputs and updates from, and eventually receive commands. This is the DataContext and, because we can bind to anything, it's going to be an object.

It's worth noting that this is a naive implementation because we are relying on the DataContext being set before we perform any bindings.

The next part we need is some mechanism to bind to our properties so that we can act on them. The glue underneath MVVM implementations relies heavily on the power of automatic binding. Indeed, this is the reason that WPF and MVVM went so well together; the binding mechanisms in WPF helped to make early adoption of MVVM so popular. So, we need to replicate a binding implementation of our own. Breaking YAGNI for the moment, I'm going to store the binding inside a dictionary with the property name as the key. I know that we are only binding one property at the moment but we will be using this to build a more complex implementation later on. If we go back to our View for the moment, we see that we set the DataContext to an instance of PersonViewModel, and that we set up a binding to the Name property inside the ViewModel. Given that we've talked about the binding mechanism on a few occasions, what does this actually look like?

C#
public class Binding
{
  public Binding(string property)
  {
    Name = property;
  }

  public object Source { get; set; }

  public string Name { get; set; }

  public void Parse()
  {
    INotifyPropertyChanged inpc = Source as INotifyPropertyChanged;
    if (inpc == null)
    {
      return;
    }
    inpc.PropertyChanged += Inpc_PropertyChanged;
  }

  private void Inpc_PropertyChanged(object sender, PropertyChangedEventArgs e)
  {
    PropertyInfo propertyInfo = Source.GetType().GetProperty(e.PropertyName);
    object value = propertyInfo?.GetValue(Source);
    Console.WriteLine($"{e.PropertyName} changed to {value}");
  }
}

This class really isn't that complicated. The Source is populated from the DataContext in the Framework class and the Name is the name of the property we want to bind to in our ViewModel. The interesting part is the Parse method. What we are doing here is checking to see if the Source implements INotifyPropetyChanged and, if it does, hooks up to the PropertyChanged event handler. There's an implication in this code that our ViewModel might not support INotifyPropertyChanged (we often shorten this to INPC).

MYTH BUSTER #2. A ViewModel doesn't have to support INPC. If nothing is changing in it, why raise a change notification from it?

We can see that this, again, isn't production quality code - for the purposes of the examples, we aren't going to worry about the lifetime of events and so on. As we go on through this series, we will turn this into a more robust system.
The eagle eyed reader will have noticed that we have restricted our ViewModel to a single level of property here. In other words, we can't bind to "SomeType.SomeOtherType.Property" right now. This is a deliberate limitation of this implementation of the code, which we will address in the next article when we start to expand our binding mechanism.

If we go back to our View, we see that we have a simple Input method for accepting input from the user and updating our ViewModel. When the Name property is updated, we will raise an INPC event that will trigger the code in the Inpc_PropertyChanged event handler where we extract the property information for the property and use this to retrieve the value from the property. The Input method is the equivalent to entering a value in a TextBox.

The ViewModel

So now we get to it. We are at the ViewModel portion of the code.

C#
public class PersonViewModel : INotifyPropertyChanged
{
  private readonly PersonModel personModel;
  private readonly PersonModelValidator validator;

  public PersonViewModel()
  {
    personModel = new PersonModel();
    validator = new PersonModelValidator();
  }

  public string Name
  {
    get { return personModel.Name; }
    set
    {
      if (!validator.IsValid(value))
      {
        return;
      }
      personModel.Name = value;
      OnPropertyChanged();
    }
  }

  public event PropertyChangedEventHandler PropertyChanged;

  protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
  {
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  }
}

This is where we start to see what we talked about earlier, that the Model part is anything that isn't the View or the ViewModel. The ViewModel is the bit that is responsible for providing the View and the Models to work together. The View shouldn't have any knowledge of how to interact with the models portion of the application, so the ViewModel is the "glue" in the middle. So, our constructor creates two models; the PersonModel and PersonModelValidator classes that we looked at earlier. When the user updates the Name, we use the validator to check that we are okay to update the name of the person, then we raise the PropertyChanged event, which will trigger the INPC event handler inside the Binding.

So what does our application look like when running? Granted, it's not the sexiest looking interface but given that we're in a Console application, the fact that we are doing this as MVVM is great fun.

Application running.

And that's it; that's the first step in creating an MVVM Console application. In the next article, we're going to expand our codebase to include support for more complex ViewModel and Model interactions, and we will introduce commanding support through the ICommand interface.

History

  • 29th December, 2016: Initial version

License

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


Written By
CEO
United Kingdom United Kingdom
A developer for over 30 years, I've been lucky enough to write articles and applications for Code Project as well as the Intel Ultimate Coder - Going Perceptual challenge. I live in the North East of England with 2 wonderful daughters and a wonderful wife.

I am not the Stig, but I do wish I had Lotus Tuned Suspension.

Comments and Discussions

 
QuestionExplanations Pin
RonVanAken26-Sep-17 15:10
RonVanAken26-Sep-17 15:10 
AnswerRe: Explanations Pin
Pete O'Hanlon26-Sep-17 20:45
mvePete O'Hanlon26-Sep-17 20:45 
GeneralMy vote of 5 Pin
User 1106097913-May-17 3:14
User 1106097913-May-17 3:14 
I'm new to this topic and I just started reading. Maybe I am too impatient or have not understood anything, but I wonder: Why is INPC not generally implemented in the model? Regardless of MVVM, my thought is, that this could be useful also for many other situation in which I like to use the model. What I'm missing?
Thank you in advance.
Bruno

modified 19-Jan-21 21:04pm.

GeneralRe: My vote of 5 Pin
Pete O'Hanlon13-May-17 6:02
mvePete O'Hanlon13-May-17 6:02 
QuestionHow to avoid useless notifications? Pin
PeppoTS10-Mar-17 5:25
professionalPeppoTS10-Mar-17 5:25 
AnswerRe: How to avoid useless notifications? Pin
Pete O'Hanlon13-Mar-17 4:45
mvePete O'Hanlon13-Mar-17 4:45 
PraiseGets My Vote Pin
Peter Shaw9-Jan-17 9:18
professionalPeter Shaw9-Jan-17 9:18 
GeneralRe: Gets My Vote Pin
Pete O'Hanlon9-Jan-17 9:34
mvePete O'Hanlon9-Jan-17 9:34 
PraiseIf only ... Pin
PeejayAdams9-Jan-17 6:03
PeejayAdams9-Jan-17 6:03 
GeneralRe: If only ... Pin
Pete O'Hanlon9-Jan-17 9:33
mvePete O'Hanlon9-Jan-17 9:33 
PraiseThank You Pin
ronlease8-Jan-17 12:28
professionalronlease8-Jan-17 12:28 
GeneralRe: Thank You Pin
Pete O'Hanlon8-Jan-17 20:31
mvePete O'Hanlon8-Jan-17 20:31 
QuestionGreat Pin
Sacha Barber6-Jan-17 6:08
Sacha Barber6-Jan-17 6:08 
AnswerRe: Great Pin
Pete O'Hanlon6-Jan-17 7:01
mvePete O'Hanlon6-Jan-17 7:01 
QuestionMVVM My Way Pin
#realJSOP6-Jan-17 6:03
mve#realJSOP6-Jan-17 6:03 
AnswerRe: MVVM My Way Pin
Pete O'Hanlon6-Jan-17 7:00
mvePete O'Hanlon6-Jan-17 7:00 
QuestionPartial classes for some validations Pin
Wendelius5-Jan-17 20:59
mentorWendelius5-Jan-17 20:59 
AnswerRe: Partial classes for some validations Pin
Pete O'Hanlon5-Jan-17 21:24
mvePete O'Hanlon5-Jan-17 21:24 
GeneralRe: Partial classes for some validations Pin
Wendelius5-Jan-17 21:32
mentorWendelius5-Jan-17 21:32 
GeneralMy vote of 5 Pin
Wendelius5-Jan-17 20:56
mentorWendelius5-Jan-17 20:56 
GeneralRe: My vote of 5 Pin
Pete O'Hanlon5-Jan-17 21:05
mvePete O'Hanlon5-Jan-17 21:05 
QuestionAt last it's starting to make sense in my tiny brain ... Pin
Richard MacCutchan4-Jan-17 3:25
mveRichard MacCutchan4-Jan-17 3:25 
AnswerRe: At last it's starting to make sense in my tiny brain ... Pin
Pete O'Hanlon4-Jan-17 3:33
mvePete O'Hanlon4-Jan-17 3:33 
AnswerRe: At last it's starting to make sense in my tiny brain ... Pin
Pete O'Hanlon5-Jan-17 5:55
mvePete O'Hanlon5-Jan-17 5:55 
PraiseGreat first part Pin
Daniel Vaughan29-Dec-16 5:46
Daniel Vaughan29-Dec-16 5:46 

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

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