Watch recordings of Brian's WCF RIA Services webinars:
This article is Part 4 of the series WCF RIA Services.
Introduction
The Model-View-ViewModel pattern (MVVM) is a very popular approach for building more loosely coupled Silverlight and WPF applications. I won’t attempt to full define and explain the pattern here, but will give a quick explanation of what it is about and why you would want to use it. At a high level, MVVM is an alternative or evolution of MVC and MVP patterns, but taking full advantage of the rich data binding, commands, and behaviors that we have available to us in Silverlight and WPF. The view model’s primary responsibility is to offer up state to the view in the way the view wants to see it. It exposes properties that the view can easily bind to that let the view display the data it needs and to also hook up commands and behaviors that point back to the view model to invoke interaction logic that the view model encapsulates. The view model is all about containing and manipulating the data that the view needs and providing the interaction logic to support the view. The view model acts as a translator from the complex world of the overall application model to the specific set of data that a single view needs, structured in the way the view needs to display it. Structurally, you typically set the view’s DataContext equal to an instance of the view model so that the view can easily bind to the exposed properties of the view model. There are many ways to get this hook up done.
The motivations for using it include the fact that it allows you to work on the view and the view model mostly in isolation, facilitating developer/designer workflow. Additionally, by separating your interaction logic into the view model, you can more easily unit test that logic because it is not tightly woven with the UI itself. And finally, the pattern just provides a clean separation of responsibility so the view can just be about the structure and visual behavior of what you see on the screen, and the view model can just be about the data and logic surrounding the data that the view needs to support it. For more in depth coverage, I recommend you read Josh Smith’s excellent article on MVVM, as well as the guidance being developed by the Microsoft patterns & practices Prism team in Prism 4 at http://prism.codeplex.com/. Prism 4 is due out in the September/October timeframe but there are already public drops in the downloads section on CodePlex.
So now let’s see how MVVM fits with WCF RIA Services. The starting point code for this article will be the sample code from Part 3 of the series, which you can download here. You can download the finished code for this article here.
Step 1: Factor out a view model
In the last article’s version of the TaskManager application, I was already starting to have a fair amount of code building up in the code behind of the view (the MainPage user control), even with such a simple application. The first step is to factor that code out into a view model. Create a new class named TasksViewModel in the project. Often a good way to get started defining your view model is to analyze the view and determine what properties would be needed to support it. The image below shows the form as it is currently defined. To clean things up a bit, I’ve replaced the two TextBoxes for date input at the top with DatePickers, removed the unused properties on the Task from the DataGrid, and reordered the columns to look a little better.
By looking at the UI design, you can identify 6 properties the view model should expose to support this view: two DateTime properties for lower and upper dates for a search; three commands to support searching by date, adding a task, and saving changes; and one Tasks collection. In its simplest form, that would look like this:
public class TasksViewModel
{
public DateTime LowerSearchDate { get; set; }
public DateTime UpperSearchDate { get; set; }
public ICommand SearchByDateCommand { get; set; }
public ICommand AddTaskCommand { get; set; }
public ICommand SaveChangesCommand { get; set; }
public IEnumerable<Task> Tasks { get; set; }
}
However, you generally need to implement INotifyPropertyChanged on your view model which requires the expanded syntax for each property so that the UI can stay fresh through its data bindings if the bound property on the view model changes, so each property definition should look more like this in its full form, except possibly the private set properties that the view model sets once and does not change.
public class TasksViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
DateTime _LowerSearchDate;
public DateTime LowerSearchDate
{
get
{
return _LowerSearchDate;
}
set
{
if (value != _LowerSearchDate)
{
_LowerSearchDate = value;
PropertyChanged(this, new PropertyChangedEventArgs("LowerSearchDate"));
}
}
}
To support the ICommand properties, you will need a decent command implementation. I’ve included a simple RelayCommand implementation, but normally in real projects I use the DelegateCommand from Prism.
A view model is all about managing the data needed by a view, and a DomainContext in RIA Services is all about providing and tracking changes to the data, so a simple approach to using RIA Services with MVVM is to simply use a DomainContext within your view model.
TasksDomainContext _Context = new TasksDomainContext();
public TasksViewModel()
{
SearchByDateCommand = new RelayCommand<object>(OnSearchByDate);
AddTaskCommand = new RelayCommand<object>(OnAddTask);
SaveChangesCommand = new RelayCommand<object>(OnSaveChanges);
Tasks = _Context.Tasks;
if (!DesignerProperties.IsInDesignTool)
{
_Context.Load(_Context.GetTasksQuery());
}
}
Here I have made the TasksDomainContext a member of the view model, and initialized the commands in the constructor to point to methods within the view model. The Tasks property exposed by the view model just holds a reference to the Tasks entity collection exposed by the domain context, which will raise INotifyCollectionChanged events to keep the view fresh when the collection changes, which will happen on the initial load, when you add Tasks, and when SubmitChanges completes and has updated entities from the back end. Notice the use of the DesignerProperties.IsInDesignTool property to prevent calling Load in the designer, which would break it.
Next is to move the methods that were in the code behind of MainPage into the view model. I’ve cleaned them up a bit in the process too. Note that the search method now leverages the deferred execution I talked about in Part 3, making the GetTasksByStartDate domain service method unnecessary since the client can specify that search expression itself. I’ve also moved the creation of a new Task into a simple popup ChildWindow so you can actually edit the values. Note the commend in the code below – showing a dialog from a view model is really a no-no, just doing it here because the focus is on RIA Services, not on full MVVM patterns.
private void OnSearchByDate(object param)
{
_Context.Tasks.Clear();
EntityQuery<Task> query = _Context.GetTasksQuery();
LoadOperation<Task> loadOp = _Context.Load(query.Where(t => t.StartDate >= LowerSearchDate && t.StartDate <= UpperSearchDate));
}
private void OnAddTask(object param)
{
// Generally don't want to do this for testability reasons
// Simplification because MVVM structuring is not the focus here
// See Prism 4 MVVM RI for a cleaner way to do this
AddTaskView popup = new AddTaskView();
popup.DataContext = new Task();
popup.Closed += delegate
{
if (popup.DialogResult == true)
{
Task newTask = popup.DataContext as Task;
if (newTask != null) _Context.Tasks.Add(newTask);
}
};
popup.Show();
}
private void OnSaveChanges(object param)
{
_Context.SubmitChanges();
}
At this point you have you have a functioning view model, now I’ll clean up the view to work with the view model.
Step 2: Hook up the view to the view model
From Part 1, the view was using the DomainDataSource that was generated from the drag and drop. If you want a clean MVVM design, you will unfortunately have to pass on using DomainDataSource. That object is effectively putting state management and query logic into the XAML itself, which violates the clean separation of responsibilities followed in MVVM.
So basically I took the existing view, deleted out the DomainDataSource, and added appropriate bindings to each of the controls to bind to the exposed view model properties as shown below. For a top level view like this, constructing the view model in the XAML to set it as the data context is a simple way to get the view’s data context set to an instance of the view.
<UserControl x:Class="TaskManager.MainPage" ...>
<UserControl.DataContext>
<local:TasksViewModel/>
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" Background="White">
<sdk:DataGrid ItemsSource="{Binding Tasks}" .../>
<Button Command="{Binding SearchByDateCommand}" .../>
<Button Command="{Binding AddTaskCommand}" ... />
<Button Command="{Binding SaveChangesCommand}" ... />
<sdk:DatePicker SelectedDate="{Binding LowerSearchDate}" ... />
<sdk:DatePicker SelectedDate="{Binding UpperSearchDate}" ... />
</Grid>
</UserControl>
Lastly, remove all the code from the code behind of the view, since it is no longer being used (all the button click handlers were removed in the process of hooking of the commands with bindings to the view model properties).
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
}
Summary
The simplest way to use WCF RIA Services with the MVVM pattern is to stop using the DomainDataSource in your views and to use a domain context as the repository of data for your view models. You can still leverage the drag and drop Data Sources window features, you just have to do a little clean up to delete off the DomainDataSource after the drop and touch up the bindings a little.
There are two downsides to the approach as I have shown it here: testability and separation of concerns. One of the benefits of the MVVM pattern is supposed to be unit testability. But with a concrete type dependency on the domain context type in your view model, you are out of luck for mocking out that dependency. There are two approaches possible to address this:
- Use dependency injection to provide the domain context to the view model, and create a mock DomainClient and pass it to the DomainContext. This allows your view model to keep using the full functionality of the DomainContext, but you are able to mock out the calls to the service underneath the DomainContext. This approach is outlined in this post. I’ll be providing an example in Part 8 of this series, which is focused on testability.
- Factor out the DomainContext to a repository service that defines an interface similar to the DomainContext API and consume that from the view model. This would allow you to mock your repository service that contains the domain context. It would also address the separation of concerns problem, because then you would be separating the implementation choice of using RIA Services from the view model, which should really be separated. However, you would give up a lot of flexibility in the view model and it would not be an easy repository implementation.
Neither of these approaches is particularly easy. I’ll cover them in more detail later in part 8, and may blog some more on this topic as well. That’s all for now. You can download the finished code for this article here. See you next time.
About the Author
Brian Noyes is Chief Architect of IDesign, a Microsoft Regional Director, and Connected System MVP. He is a frequent top rated speaker at conferences worldwide including Microsoft TechEd, DevConnections, DevTeach, and others. He is the author of Developing Applications with Windows Workflow Foundation, Smart Client Deployment with ClickOnce, and Data Binding in Windows Forms 2.0. Brian got started programming as a hobby while flying F-14 Tomcats in the U.S. Navy, later turning his passion for code into his current career. You can contact Brian through his blog at http://briannoyes.net.