Introduction
There are many discussions and arguments with regards to whether a Model should implement a property-changed interface or not. In theory, implementing a property-changed interface (INotifyPropertyChanged) in the Model violates the MVVM pattern. This article presents when that scenario usually happens and tries to solve it in order to keep the MVVM pattern valid and reliable.
Background
First, a short overview of the MVVM pattern. I will not go in much detail here since lots has been written on the web and in books. The point of this article is to clarify the property-changed interface, so if you are totally unfamiliar with the MVVM pattern I would then suggest you start with the basics before proceeding here. My source of information is the article of Robert McCarter, Design Patterns - Problems and Solutions with Model-View-ViewModel.
Model - Simple class objects that hold data and sometimes contain logic. Model does not directly reference View or in other way, Model has no dependency on view as how they are implemented. Technically speaking, Model classes are used in conjunction with a service or a repository that encapsulate data access.
ViewModel - Main purpose of ViewModel classes is to expose data to the View. It includes presentation logic and can be tested independently of Model. Similar to Model, ViewModel never references View but it exposes properties and commands to bind the View data. In essence, ViewModel acts as a coordinator between View and Model.
View - There should be no logic code in View classes. Views are meant only for UI visual behavior.
The challenge
The MVVM pattern suggests not mixing UI code (Presentation Logic) and data code (Business Logic), the aim is to separate those two domains. The standard MVVM approach is to implement property-changed interface only on the ViewModel. Therefore, if the interface is implemented in the Model, it does violate the pattern and pumps up the Model.
However, if a Model class has 20 properties that need to be exposed in the View, the ViewModel typically ends up having 20 identical properties that simply proxy the call to the underlying Model instance. These proxy properties usually raise a property-changed event when set to indicate to the View that the property has been changed. Therefore, every Model property that needs to be exposed in the View, should have a proxy property. But assuming that a professional application might have multiple Model classes that need to be exposed to the View through the ViewModel, this makes the implementation a nightmare. In that case, many developers end up adding the property-changed event in the Model classes. With this approach, the Model becomes complex and it reduces your ability to introduce new ViewModel-specific functionality later.
Using the code
The idea of the application is very simple. There is only one window divided in two sections, section 1 and section 2. The section 1 contains 16 controls, the half of them are texts and the rest are circles which play the role of green and red light. Finally, a "Generate" button generates some values. These values are numbers which come from a mock-service that generates them in the range of -100 to 100. If the generated random number is positive then its corresponding light becomes green. Accordingly, if the number is negative the light becomes red.
The section 2 is just a simple sum calculator. The user inputs the numbers and the "Sum" button returns the result. There is not any complex logic here, just a sum fucntion.
The current sample uses the MVVM Light Toolkit which is a lightweight toolkit that speeds up the development of MVVM applications.
Now, let's see a common mistake while building the Section 1. Several developers used to implement the RaisePropertyChanged
in the Model. When the View binds directly to the Model you are mixing UI code and data code. The result is like this:
public class Section1Model : ViewModelBase
{
private int _value1;
private SolidColorBrush _lightIndicator1;
public int Value1
{
get
{
return _value1;
}
set
{
_value1 = value;
RaisePropertyChanged("Value1");
}
}
public SolidColorBrush LightIndicator1
{
get
{
return _lightIndicator1;
}
set
{
_lightIndicator1 = value;
RaisePropertyChanged("LightIndicator1");
}
}
}
As it is already mentioned, that violates the MVVM pattern. I know sometimes it might be useful but in similar cases like this one, we can find -and I will present it later- a better way that adheres to the MVVM pattern. The next image shows how the pattern's architecture looks like.
In addition, I would like to mention another mistake which is against best practice in MVVM. This is the usage of the SolidColorBrush
. Once again, it is a UI-related duty (visual behavior) and it should reside within the View.
Now, what I prefer to do in order to skip the implementaion of the property-changed event in the Model
, is to create a wrapper for the Model
in the ViewModel
and bind its properties in the XAML. Here is an example, starting from the updated Model
:
public class Section1Model
{
private int _value1;
private SolidColorBrush _lightIndicator1;
public int Value1
{
get
{
return _value1;
}
set
{
_value1 = value;
}
}
public SolidColorBrush LightIndicator1
{
get
{
return _lightIndicator1;
}
set
{
_lightIndicator1 = value;
}
}
}
And now the ViewModel
implements the property-changed event.
public class MainViewModel : ViewModelBase
{
private Section1Model _section1Model;
public Section1Model Section1Model
{
get
{
return _section1Model;
}
set
{
_section1Model = value;
RaisePropertyChanged("Section1Model");
}
}
}
Finally the bindings in the XAML become:
<TextBlock Text="{Binding Path=Section1Model.Value1, Mode=OneWay, FallbackValue=value1}">/>
<Ellipse Fill="{Binding Path=Section1Model.LightIndicator1, FallbackValue=Gray}">/>
Now to make the Model
clean from UI-related code, we drop the SolidColorBrush
. We can create our own type for representing the color in Model (e.g. bool). Then we can write a custom ValueConverter
to convert Model
color type into presentation framework dependent color representation. Converter
should be used together with bounding expression. An example can be found here.
class BoolToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
return new SolidColorBrush(Colors.Gray);
}
return System.Convert.ToBoolean(value) ? new SolidColorBrush(Colors.Green) : new SolidColorBrush(Colors.Red);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The bool plays the role of the green/red light (true/false). A clean Model looks like this:
public class Section1Model
{
private int _value1;
private bool _lightIndicator1;
public int Value1
{
get
{
return _value1;
}
set
{
_value1 = value;
}
}
public bool LightIndicator1
{
get
{
return _lightIndicator1;
}
set
{
_lightIndicator1 = value;
}
}
}
The bindings in XAML become:
<src:BoolToColorConverter x:Key="boolToColorConverter"/>
<Ellipse Fill="{Binding Path=Section1Model.LightIndicator1, Converter={StaticResource boolToColorConverter}, FallbackValue=Gray}">/>
Now the application cleanly separates the business and presentation logic from its user interface.
The Section 2 doesn't necessarily needs the creation of a Model
. Its purpose in this sample is just to give you a comparison to Section 1. Obviously, if you need to make a real calculator, you might need a Model
class, or a Service that calculates all the functions. It is up to you.
Conclusion
Once again, it is not always bad to implement the property-changed event in the Model. But if you need a clean MVVM pattern you must separate them. The benefits of a clean MVVM pattern are:
- It provides separation of concerns. A clean separation between application logic and the UI will make an application easier to test, maintain, and evolve.
- It is a natural pattern for XAML platforms.
- It enables a developer-designer workflow. When the UI XAML is not tightly coupled to the code-behind, it is easy for designers to exercise the freedom they need to be creative and make a good product.
- It increases application testability.