This article is compatible with the latest version of Silverlight.
Introduction
In this short series of articles I’ll cover what MVVM is and how to use it in practice, how to solve issues when applying this pattern and how to take advantage from it.
In the previous articles we created a simple master details scenario with the MVVM pattern. We used mock and live data sources with WCF, we created unit tests, we used Messaging from the MVVM Light Toolkit.
By now, we have an application that communicates with a server, and we know how to send data back to a server. This raises another questions like, are we sending back valid data?
Validation in MVVM
Let’s think about this a little bit. How and what do we want to validate? Let’s see. We have an Author, a Title, and a Price. Well we can set the price property to a negative value, which is probably not a valid scenario. In Silverlight 3 we can do validation in property setters. What we basically do is throwing exceptions in the setter if the value is not valid. This kind of validation has two problems.
- You throw exceptions to validate in property setters…. well… yeah…
- You can’t support full entity validation. You need to do workarounds to support that scenario.
Well, in Silverlight 4 we have an IDataErrorInfo interface, that supports validation, without throwing exceptions. Also you can centralize your validation rules.
Let’s validate the Price property! If the price is lower than zero, then this value is invalid.
//Modify the BookDetailsViewModel class to implement IDataErrorInfo
public class BookDetailsViewModel : ViewModelBase, IDataErrorInfo
//Implement IDataErrorInfo
public string Error
{
get { return null; }
}
public string this[string columnName]
{
get
{
if (columnName == "Price")
{
//We have a validaton error
if (Price < 0) return "Price must be positive";
}
//Everthing is okay
return null;
}
}
Also you have to modify the binding for the textbox displaying the price. You need to add the ValidatesOnDataErrors=True option, to enable the use of IDataErrorInfo based validation.
<TextBox Grid.Column="1" TextWrapping="Wrap" Grid.Row="2" d:LayoutOverrides="Height"
VerticalAlignment="Center" Margin="15,0"
Text="{Binding Price, Mode=TwoWay, ValidatesOnDataErrors=True}"/>
You can create you own validation handler classes and centralize the validation logic as much you want. However this kind of validation supports synchronous validation only. Which means, the binding happens, the validation happens and that’s it. If there is a later change in the validation state, this scenario cannot be applied. Let me give you an example. You have the Title property. Let’s assume we have an Update operation. When we update a book, we have to make sure, that the new title we enter is not the same as any other title. The title should be unique (well not in real life, but right now it does :D). You can’t validate this one on the client side! You have thousands of books in the database. You have to run a check against a huge amount of data and it might take more than a second. Which means that when the user sets the title property, we should let him do it. But immediately we should run an asynchronous check to validate the title. If the check’s result is that the title entered is not valid, then we have to indicate it somehow.
Also what if I need more detailed error report, not just a simple string? What if a change in a property changes another property bound to the UI and now the other property’s value is invalid? I should indicate it somehow.
IDataErrorInfo does not support these scenarios. Fortunately there is an other interface that supports all these scenarios. It’s called INotifyDataErrorInfo.
This is a little bit more complex, so let’s go step by step.
public interface INotifyDataErrorInfo
{
bool HasErrors { get; }
event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
IEnumerable GetErrors(string propertyName);
}
There are two very important members here The ErrorsChanged event, and the GetErrors method!
GetErrors() returns a list of errors of a specific property, it can be a list of strings, or a list of custom objects, it’s up to us. The ErrorsChanged event should be raised whenever a new validation error should be added, or a validation error should be removed. How you handle the validation errors’ list is pretty much up to you.
Let’s implement it. However this will need a lot of additional code that is not specific to a certain ViewModel, so most of the code, I’ll put into an extended ValidatingViewModelBase class which implements INotifyDataErrorInfo interface and inherits ViewModelBase.
First I’ll need a list that can contain the errors for each property.
public class ValidatingViewModelBase : ViewModelBase, INotifyDataErrorInfo
{
//Key is propertyName, value is list of validation errors
protected Dictionary<string, List<string>> errorList = new Dictionary<string, List<string>>();
The key of the dictionary is the name of the property. The Value is a list of errors represented by simple strings.
I have an error if I have something in the Values inner collection.
public bool HasErrors
{
get
{
//If there is any validation error
return errorList.Values.Count > 0;
}
}
Now I need to implement the GetErrors() method:
/// <summary>
/// Gets the list of validation errors for a property
/// </summary>
/// <param name="propertyName">Name of the propety</param>
public System.Collections.IEnumerable GetErrors(string propertyName)
{
CheckErrorCollectionForProperty(propertyName);
//return related validation errors for selected property.
return errorList[propertyName];
}
protected void CheckErrorCollectionForProperty(string propertyName)
{
//First time to get the validation errors of this property.
if (!errorList.ContainsKey(propertyName))
{
errorList[propertyName] = new List<string>();
}
}
The CheckErrorCollectionForProperty() method’s responsibility is to check whether the property is already registered in the error collection. If not, than it should do it, and assign an empty string list as a value, to store the list of validation errors. The GetErrors() method now returns with the list of errors associated with the property name.
Now we need ways to add and remove validation errors.
/// <summary>
/// Add a validation error to a property name.
/// </summary>
/// <param name="propertyName">Name of the property</param>
/// <param name="error">The validation error itself</param>
protected void AddError(string propertyName, string error)
{
CheckErrorCollectionForProperty(propertyName);
errorList[propertyName].Add(error);
RaiseErrorsChanged(propertyName);
}
/// <summary>
/// Remove a specific validation error registered to a property name.
/// </summary>
/// <param name="propertyName">Name of the property</param>
/// <param name="error">The validation error itself</param>
protected void RemoveError(string propertyName, string error)
{
if (errorList[propertyName].Contains(error))
{
errorList[propertyName].Remove(error);
RaiseErrorsChanged(propertyName);
}
}
/// <summary>
/// Notifies the UI of validation error state change
/// </summary>
/// <param name="propertyName">The name of the property that is validated</param>
protected void RaiseErrorsChanged(string propertyName)
{
if (ErrorsChanged != null)
{
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
}
The Add method makes sure that there is a list associated with the property name, then adds the error string to this collection. One last thing to do there, is to notify the UI, that something has changed related to the validation error of this property.
The Remove method removes the specific error from the error collection.
The infrastructure is ready by now, we can use it. The BookDetailsViewModel now inherits the ValidatingViewModelBase class.
public class BookDetailsViewModel : ValidatingViewModelBase, IDataErrorInfo
{
public string Title
{
get
{
return book.Title;
}
set
{
book.Title = value;
RaisePropertyChanged("Title");
ValidateTitle(value);
}
}
//Validate Title Property
private void ValidateTitle(string title)
{
//Check server side data for validation result
bookDataSource.CheckIfTitleAvailable(this.BookID, title);
}
//Callback from the server validation process
void bookDataSource_CheckIfTitleAvailableCompleted(object sender, CheckIfTitleAvailableCompletedEventArgs e)
{
//Validation Logic
if (!e.IsAvailable)
{
//Invalid
AddError("Title", "The Book with that title already exits!");
}
else
{
//Valid
RemoveError("Title", "The Book with that title already exits!");
}
}
In the setter of the Title property I call the ValidateTitle() method. The validate Title uses the bookDataSource, to check on the server if the title is available for this item, or another book already has it. (I made minor changes, to enable an extra WCF service operation. You can check it in the VS solution posted with this article).
Now in the completed event handler I check if the title is available. If not, then I call the previously created AddError() method. If it’s available, I call the RemoveError() method. This is happening absolutely asynchronously. The AddError() and the RemoveError() methods raise the ErrorsChanged event, so the UI gets notified also asynchronously about the change in the state of the validation.
We have to indicate on the binding, that we are using this kind of validation mechanism. This is done through the ValidatesOnNotifyDataErrors=True option
<TextBox Grid.Column="1" TextWrapping="Wrap" d:LayoutOverrides="Height"
VerticalAlignment="Center" Margin="15,0"
Text="{Binding Title, Mode=TwoWay, ValidatesOnNotifyDataErrors=True}"/>
That’s it, you have asynchronous validation across tiers.
Bringing the UI and the ViewModels more closely
If you start to work with MVVM, you can get the taste of it very soon. You’ll really like it, but even in the beginnings there will be battles, you’ll have to fight. The problem will emerge mostly in the area of integrating the ViewModel with the specific View more closely. For example, how do you plan to display messages, or dialog boxes? What if you want to do something in the ViewModel as soon as a Storyboard ends? This is the area where behaviors, actions, and triggers can help you a lot.
For me, among many others, there are two very important and useful behavior to use with MVVM, which I’d like to introduce you now.
1. The EventToCommand behavior.
What if, I don’t want to click the load data button in my application? What if I want to load the data as soon as the application starts. (I know what is in your mind right now, please don’t ever put code in the ViewModel’s constructor that calls a real data source (WCF Service, etc…) That would make blendability a nightmare). The EvenToCommand behavior located in the MVVM Light Toolkit can help you a lot. You can attach it to a control, tell which event you are waiting for, and as soon as that event fires, the associated command object gets invoked.
<Grid x:Name="LayoutRoot" Background="White">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding LoadBooksCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
As soon as the Grid’s Loaded event happens, the LoadBooksCommand gets invoked.
2. The VSMChangerBehavior
Imagine you have a special menu, panel, whatever. In some cases it’s visible, in some cases it’s not. Very easy you say, let’s create a bool property in the ViewModel, and use a converter to convert the bool value to Visibility for the UI. That definitely works. But you just tied the hands of your designer. What if his plan is to slide the panel in and out for the true and false values? What you should really do, is to define Visual States for the Visible and NonVisible states and use the VisualStateManager to move from one state to the other. Now the designer can do whatever he wants in these Visual States. What a big difference in flexibility, huh? But now you have a problem. You have a bool value, or an enum in the ViewModel. Now you have to write code on the UI, to check these states. Or let the view model “know” about the view and control it like a Controller. This is not something you want. This is where the VSMChangerBehavior created by Andras Velvart can help you out. You define states (by convention) for you enums, and let the VSMChangerBehavior do the transitions automatically, as the properties in the ViewModel change.
If you want to know more about this behavior, go and visit silverlightshow.
There are a lot of other behaviors / actions you should be familiar with, like GoToStateAction, CallMethodAction, ControlStoryboardAction, DataStateBehavior. These can help you a lot.
Final thoughts
Also there are other concepts you should consider, like I prefer to manage an IsBusy property in the ViewModel, so I have the option to indicate the user if there is something going on behind. Or for your collections you should implement paging using a PagedCollectionView class. To make your application and your components really flexible and loosely coupled, you might want to get familiar with MEF (for extensibility) or Unity (IoC container). So there is still a long way to go, but I hope these 3 articles are enough to get you started.
Today MVVM is close to be called a mature pattern. Let me rephrase it. A well supported pattern. I think if it comes to business application development, this pattern definitely should be used. Silverlight 4 is now a very good platform on which you can develop Line of Business Applications.
Download the source code