Click here to Skip to main content
15,888,527 members
Articles / Desktop Programming / WPF

MVVM and BaseViewModel foundations

Rate me:
Please Sign up or sign in to vote.
2.25/5 (4 votes)
31 Aug 2015CPOL7 min read 23.9K   7   2
Setting up the foundation of MVVM for global architecture.

Introduction

The following article covers how to set up your MVVM layers and interactions to ease development for frameworks and global architecture.

Background

This is not meant as an introductory lesson to MVVM and is geared for real-world environments.

Layer Interactions

The key principle to layer interactions is single reponsibility.  While general architecture guidelines don't rigidly dictate interactions between layers I find in MVVM if you don't stick to a single layer, downward only pattern you will inevitably blend responsibilities.

While most using MVVM might know what the layers are I often seen their responsibilities blended.  So the first thing I want to do is define the overall purpose and interactions of each layer as well as what they aren't supposed to be...

What these layers are:

Model - service and data layer interactions.

View Model - business logic for composing data needed for UI interactions.

View - control and presentation logic.

What these layers are not:

Model - a property backer for your view model layer.

View Model - a place to control all logic for your controls.

View - devoid of all code besides XAML.

You don't want your model layer as a property backer because the view model is an editable and composable layer.  Doing so leaves your model layer in a potentially dirty state, but at best you have to manage your model layer in parallel with your view model layer.

The gut check reaction for developers converting from WinForms, or from other technologies, to WPF is to shove everything that used to be in the view objects into the view model.  As much as views aren't supposed to know about business logic, the business logic isn't supposed to know about things like which controls are read only or what's visible, etc.  Instead of view monstrosities you get view model monstrosities which is defeating the purpose.  

With well segregated responsibilites you will need to add some glue between these layers particularly for enterprise level software.  For this first article I'm not going to go in-depth into the glue, but I don't want to leave you stranded either.

If your view needs to interact with business logic it should be through commands.  Commands are well established as the glue between view models and views.  It does blur some view and view model responsibilities together at times, but it confines the scope of interaction well and is still promoting single responsibility.

The way services communicate has been continually changing and is much more diverse.  The latest focus is on asynchronous communication and pub/sub methodologies, but there's no generically accepted practice.  I prefer pub/sub methodologies because on top being asynchronous pub/sub supports loose coupling and abstraction very well.

Asynchrounous communication is particularly important for view models so that long running business operations can occur without blocking the dispatcher thread.  In pub/sub a request to save an object, for example, can be sent and processed by anyone receiving the message.  Whenever the operation completes the view model or any other listener can respond accordingly.  It also helps promote single responsibility because listeners can be funneled to common processing from a variety of message sources.

The complexity of commands and pub/sub does require more attention, but the takeaway point is there are technologies and ways to communicate between these layers without mixing their responsibilities.

Using the code

C#
/// <summary>
/// Basic view model functions.
/// </summary>
public abstract class BaseViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public virtual bool IsNew { get; protected set; }

    public virtual bool IsDirty { get; set; }

    protected virtual void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

/// <summary>
/// Basic view model which wraps a model object.
/// </summary>
public abstract class BaseViewModel<T> : BaseViewModel
{
    public BaseViewModel(T model)
        : base()
    {
        Model = model;
    }
 
    /// <summary>
    /// Gets or sets if the view model houses a new object.
    /// </summary>
    /// <remarks>A view model is designated as new by the model object not previously existing.</remarks>
    public override bool IsNew
    {
        get
        {
            return Model == null;
        }

        protected set
        {
            throw new NotImplementedException();
        }
    }

    public T Model { get; protected set; }

    /// <summary>
    /// Creates a new model object that can be used for persistence so the underlying model object doesn't get corrupted.
    /// </summary>
    /// <returns>The new model object to persist or pass.</returns>
    /// <remarks>NOTE:  This should start by creating a new model object and not use the underlying model!</remarks>
    public virtual T ToModel()
    {
        throw new NotImplementedException();
    }

    /// <summary>
    /// Reloads the view model with the specified model.
    /// </summary>
    /// <param name="model">The updated model object.</param>
    /// <remarks>NOTE:  Separation must be kept between the view model and model.  Changes by the UI should not impact the
    /// model object.  Any updates or saves that are success should come back through services to the view model.</remarks>
    public virtual void Reload(T model)
    {
        // The override implementation should set view model properties to null in order to reload
        // the values in the UI, call this base method, then raise property changed events.
        Model = model;
        IsDirty = false;
    }
}

 

 

There are obviously cases where view models won't wrap a single model such as composite view models, collections, and object graphs; however, the majority of view model objects created will be wrapping a model so I wanted to focus on this particular class (if there's enough interest in this article I'll write articles on the others too).

The clear separation of model and view model provides for some very handy functionality to include detecting if it's a new object, detecting dirty fields, and to reload/undo changes.  To support these functions the concrete implementations should all have lazy loaded properties with appropriate property backers.

Say for instance you have a Name field in your model object.  There should be a corresponding Name property in the view model.  On intialization load the Name property with Model.Name.  If you want to see if it's dirty you compare Model.Name to Name.  If you want to undo the changes or be able to reload the value all you have to do is set Name to null then it will reload the model value.  The reload functionality can be very nice for applications that can have real time updates from multiple clients.

Below is an example of a concrete implementation:

C#
public class WidgetViewModel : BaseViewModel<Widget>
{
    public WidgetViewModel(Widget widget)
        : base(widget)
    {
 
    }
 
    private string _name;
    public string Name
    {
        get
        {
            if (_name == null && Model != null)
            {
                _name = Model.Name;
            }
 
            return _name;
        }
 
        set
        {
            if (_name != value)
            {
                _name = value;
                RaisePropertyChanged("Name");
            }
        }
    }

    public void Undo()
    {
        Reload(Model);
    }

    public override void Reload(Widget model)
    {
        _name = null;

        base.Reload(model);

        RaisePropertyChanged("Name");
    }
 
    public override Widget ToModel()
    {
        var widget = new Widget();
        widget.Name = Name;
 
        return widget;
    }
}

The ToModel() method is intended to convert the edited view model object to a model layer object that can then be consumed by the service and data layers.  It is very important not to corrupt the underlying model object because it's possible for actions taken to get kicked back.  For example, if you try to save an object and it's kicked back as being invalid or unable to save.  If you modified the model object in the object graph you now have a dirty object you have to deal with.  In this architecture you can just ditch the view model or reload and BAM done.

The reason why you want the services to deal exclusively with model objects is because the view model is inherently coupled with the view, and service logic shouldn't have any association with presentation.  Even if your code is segregating view and view model responsibilities very well the view model is created specifically to house the information needed in the view.  That view of the data, when used in service layers, can muck up the scalability and flexibilty of services very easily.

View model objects are also inherently bloated so they can be much more expensive to transmit.  For example, if I have a foreign key to a dependent entity in a model object all I need to pass around is the key field.  In the view model object you likely will have another view model of that entity which could include name, description, notes, etc.  In other words, a whole lot of data you don't need to be sending around.

Another important note is what's not included in these base view models.  Note how there's not an IsReadOnly property although that's a common property.  It's a view property!  Controls are read only not entities.

Entities might have permissions, workflows, and other access controls that are directly related to whether or not a control is read only but these shouldn't be mucked up with the business logic.  If you put these all together pretty soon your services are using an IsReadOnly field to determine what methods to call and that's bad!

Points of Interest

Adhering to these guidelines is a way to help you continue to write clean, reusable code.  There are several key concepts that can be accomplished without strict interactions; however, strictly implementing these guidelines will help prevent falling prey to pitfalls.  You might think it won't hurt to loosen up the interactions here or there, but trust me it will bite you.

Not worried about object bloat because you don't have much traffic or your hardware is bangin?  Fair enough, but what if grows beyond expectations which by the way happens a lot?  Not to mention that passing around dependent entities can lead to synchronization issues between clients and caches.

Take the time to abstract view controls from your view model.  It's a lot more work to do that, yes...  But in the end it will help you build more reusable controls.  

Say you are loading historical data so you need to pass a date range to a service call.  Easy enough to just bind controls to a start date and end date property of that view model; however, a date range control is a wonderful control to have with a large amount of reuse.  Not to mention that a user request has nothing to do with the actual entity so it's poor view model design if you put them all in the same view model.

Regardless of what dramatic code-from-the-hip, non-pattern-oriented developers say taking the time to properly scope out and design MVVM layers has always been worth it for me.  Start out doing it with everything, develop your style, then start pulling back as you see fit when you have some experience under you belt.

More to come, maybe...

This is my first shot writing a code project article so please let me know what you think!  I'll be glad to expand on anything or just shut up!

Updates

  • 2015-09-01 Added concrete example.

License

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


Written By
Architect
United States United States
More than fourteen years of experience programming with six years of WPF application development. Dedicated business application developer with large focus on performance tuning for multithreaded applications. Experience developing for small scale applications and large scale distributed systems at senior and architect levels. Experience developing for a wide range of industry applications to include artificial lift, baggage tracking, distribution, customer relation management, and commodities.

Comments and Discussions

 
QuestionA viewModel with Collection Pin
kashifmubarak@outlook.com1-Dec-16 5:18
kashifmubarak@outlook.com1-Dec-16 5:18 
AnswerRe: A viewModel with Collection Pin
PureNsanity8-Dec-16 18:13
professionalPureNsanity8-Dec-16 18:13 

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.