Implementing the Model-View-ViewModel Pattern

The Model-View-ViewModel (MVVM) pattern is an application pattern that isolates the user interface from the underlying business logic. MVVM belongs to a class of patterns called Separated Presentation. These patterns provide a clean separation between the UI and the rest of the application. This improves the testability of the application and allows the application and its UI to evolve more easily and independently. The MVVM pattern consists of the following parts:

  • The Model, which provides a view-independent representation of your business entities. The design of the model is optimized for the logical relationships and operations between your business entities, regardless of how the data is presented in the user interface.
  • The View class which is the user interface. It displays information to the user and fires events in response to user interactions.
  • The ViewModel class, which is the bridge between the view and the model. Each View class has a corresponding ViewModel class. The ViewModel retrieves data from the Model and manipulates it into the format required by the View. It notifies the View if the underlying data in the model is changed, and it updates the data in the Model in response to UI events from the View.

The following diagram illustrates the relationship between the View, the ViewModel, and the Model.

The MVVM pattern

Ff798384.19193d97-0786-474b-a47e-c91745977877(en-us,PandP.10).png

In some ways the MVVM pattern is similar to the Model-View-Presenter (MVP) pattern described in The Sandbox Execution Model reference implementation—both patterns are variants of the Model-View-Controller (MVC) pattern, both are Separated Presentation patterns, and both are designed to isolate the details of the user interface from the underlying business logic in order to enhance manageability and testability. However, whereas the MVP pattern is best suited to traditional server-rendered Web pages and the request/response paradigm, the MVVM pattern is optimized for stateful rich client applications where client-side business logic and application state is maintained through user or service interactions. The pattern enables you to maximize the benefits of Windows Presentation Foundation (WPF) and Silverlight capabilities such as two-way data-binding functionality, events, and stateful behavior provided by these frameworks. For example, you can use declarative data binding to connect the View to the ViewModel, rather than writing code to glue the two together.

Implementations of the MVVM pattern have the following characteristics:

  • The View class generates events in response to user interactions, and these events are handled by the corresponding ViewModel class. The View class has no knowledge of how the events are handled, or what impact the events will have on the Model.
  • The ViewModel class determines whether a user action requires modification of the data in the Model, and acts on the Model if required. For example, if a user presses a button to update the inventory quantity for a part, the View simply notifies the ViewModel that this event occurred. The ViewModel retrieves the new inventory amount from the View and updates the Model. This decouples the View from the Model, and consolidates the business logic into the ViewModel and the Model where it can be tested.
  • The Model notifies the ViewModel if the data in the underlying data store has changed. Generally, when you work with a stateless request/response model, you don't need to worry about whether data has changed while the request is being processed, since the window of time is small. With rich Internet application (RIA) approaches, the Model data typically stays in memory for longer, and multiple active Views may share the Model data. A user may make changes in one View that affects a different View within the application. The Model fires events to notify any active ViewModels of data changes.
  • The ViewModel notifies the View when information has changed. This is typically automated through the two-way binding infrastructure described previously.

In the previous topic, Using Client Logic to Reduce Server Load, we looked at a Silverlight implementation of an interface that allows users to view parts, suppliers, and inventory locations. To help you to understand the MVVM pattern, let's take a look at an implementation of the same scenario using MVVM. This is taken from the Silverlight REST Alt interface in the reference implementation.

As before, the user interface is defined by the MainPage.xaml Silverlight control that displays parts and suppliers. A second Silverlight user control, PartLocations.xaml, displays the inventory locations for the selected part. These two controls represent the View components of our MVVM implementation. Both views use declarative data binding to connect to the information provided by the corresponding ViewModels. For example, the following code from the MainPage.xaml control shows the extensible application markup language (XAML) that defines the grid that displays parts.

<data:DataGrid AutoGenerateColumns="False" Height="247" HorizontalAlignment="Left"  
               Margin="12,41,0,0" Name="PartsDataGrid" VerticalAlignment="Top"     
               Width="550" 
               ItemsSource="{Binding Parts}" 
               SelectedItem="{Binding CurrentPart, Mode=TwoWay}" >
  <data:DataGrid.Columns>
    <data:DataGridTextColumn CanUserReorder="True" CanUserResize="True" 
                             CanUserSort="True" Width="Auto" 
                             Binding="{Binding Id}" />
    <data:DataGridTextColumn CanUserReorder="True" CanUserResize="True" 
                             CanUserSort="True" Width="Auto" 
                             Binding="{Binding SKU}" />
    <data:DataGridTextColumn CanUserReorder="True" CanUserResize="True" 
                             CanUserSort="True" Width="Auto" 
                             Binding="{Binding Title}" />
  </data:DataGrid.Columns>
</data:DataGrid>

Notice that the ItemsSource property data-binds the grid to the Parts property, which is an observable collection defined by the ViewModel class. Note also that the SelectedItem property is data-bound to CurrentPart property, and that this is a two-way data binding. This means that the view gets updated if the source changes, and the source gets updated if the view changes. This allows us to use the selected item to drive other views, such as the suppliers and the inventory locations for the selected part.

The code-behind file for the MainPage.xaml file contains only the minimal logic required to perform the initial wire-up between the View and the ViewModel.

public partial class MainPage : UserControl
{
    private PartInventoryViewModel viewModel;

    public MainPage()
    {
        InitializeComponent();
        viewModel = new PartInventoryViewModel();
        this.DataContext = viewModel;
    }

    private void PartSearchButton_Click(object sender, RoutedEventArgs e)
    {
        viewModel.GetParts();
    }
}

This code performs the following actions:

  • It instantiates the ViewModel class, PartInventoryViewModel.
  • It sets the DataContext of the MainPage control to the ViewModel instance. Any controls contained within the MainPage control, such as the PartLocations control, will inherit this data context.
  • It notifies the ViewModel instance when the user clicks the PartSearchButton.

By setting the DataContext property, we are instructing the View to data-bind properties within the View to the specified ViewModel instance. For example, when the runtime resolves the binding expression ItemsSource="{Binding Parts}" in the View, it will attempt to find the Parts property in the specified ViewModel instance.

So far, the interactions between the View and the ViewModel shown in the following illustration have been described:

View interactions

Ff798384.de34437c-6bb9-457b-a22d-a40556f50e96(en-us,PandP.10).png

The ViewModel contains the majority of the application logic. In this implementation, we chose not to implement a separate data repository. As such the Model component of the application is provided by the entities contained in the data context, PartsDataContext, which was generated by WCF Data Services for our SharePoint list data. In more complex applications, a separate data repository would make sense in order to centralize the queries used to access data.

To understand the role of the ViewModel, let's examine what happens when the user searches for a part by typing a full or partial SKU and clicking the PartSearchButton. As you can see from the previous example, the event handler in the View calls the GetParts method on the ViewModel. The SearchSku property, which contains the search text for the query, was set through two-way data binding to the PartSkuTextBox text box. The GetParts method clears the Parts collection and then queries the Model asynchronously, as shown by the following code example.

public void GetParts()
{
    Parts.Clear();
    CurrentPart = null;

    //Define Query
    var query = (DataServiceQuery<PartsItem>)
            context.Parts
                .Where(p => p.SKU.StartsWith(SearchSku))
                .Select(p => new PartsItem
                {
                    Title = p.Title,
                    SKU = p.SKU,
                    Id = p.Id,
                    Description = p.Description
                });

    //Execute Query
    query.BeginExecute(DisplayParts, query);
}

The Parts property represents an ObservableCollection of PartsItem entities. As described in the previous topic, Using Client Logic to Reduce Server Load, an ObservableCollection fires a PropertyChanged event when the collection is changed. Because the collection is bound to a grid in the MainPage View, this event notifies the grid that it needs to update its data. As such, when we clear the collection, the user interface will update accordingly. This is illustrated by the following diagram.

Note

Since updating the Parts collection will update the UI, it's important to ensure that we only update the collection from the UI thread. However, because the GetParts method is invoked as a result of a UI event, we know it is executing on the UI thread. Therefore, we do not need to use Dispatcher.BeginInvoke to update the collection.

View interactions and PropertyChanged notifications

Ff798384.e823d920-7264-4768-9aac-8d1c743b55d9(en-us,PandP.10).png

When the query returns, the callback delegate invokes the DisplayParts method in the ViewModel.

private void DisplayParts(IAsyncResult result)
{
  Deployment.Current.Dispatcher.BeginInvoke(() =>
  {
    DataServiceQuery<PartsItem> query = 
      (DataServiceQuery<PartsItem>)result.AsyncState;
    
    var parts = query.EndExecute(result);
    foreach (var part in parts)
    {
      Parts.Add(part);
    }
  });
}

The callback method must update the Parts observable collection, which will in turn automatically update the View. It calls Deployment.Current.Dispatcher.BeginInvoke in order to ensure that the update takes place on the UI thread. The application now includes all the logic required for part search, as shown in the following diagram.

MVVM implementation for part search

Ff798384.a92dfa5e-e8ee-4204-8ec6-1c4eb821d738(en-us,PandP.10).png

As you saw earlier, when the user selects a part, the CurrentPart property is updated in the ViewModel because we defined a two-way data binding in the View. The following code shows the CurrentPart property in the ViewModel.

private PartsItem currentPart = null;
public PartsItem CurrentPart
{
  get { return currentPart; }
  set
  {
    if (value == currentPart) return;
    currentPart = value;
    GetPartSuppliers();
    GetLocations();
    OnPropertyChanged("CurrentPart");
  }
}

As you can see from the code, if the current part is unchanged, the property setter will take no action. This is a recommended practice to avoid unnecessary service calls. If the current part has changed, the property setter will retrieve the suppliers and the inventory locations for the new part. The data grids that display suppliers and inventory locations are also bound to observable collections, so the Views will update automatically as before when the ViewModel is updated.

The aspect of the MVVM that we have yet to see is how the ViewModel updates the data in the Model. When a part is selected, the PartsLocation control allows you to select an inventory location and update the inventory quantities for that part. You can also add a new inventory location by clicking Add New Location.

The PartsLocation control

Ff798384.ea932178-209c-4235-a9d9-f23db9413612(en-us,PandP.10).png

The PartsLocation user interface uses declarative data binding to track changes to the bin number and quantity fields in the ViewModel. The following code example shows the markup for the parts location data grid. As you can see, there is a two-way data binding between the SelectedItem property in the data grid and the CurrentLocation property in the ViewModel.

<sdk:DataGrid AutoGenerateColumns="False" 
              Height="120" 
              HorizontalAlignment="Left" 
              Margin="12,12,0,0" 
              Name="locationsDataGrid" 
              VerticalAlignment="Top" 
              Width="348" 
              ItemsSource="{Binding CurrentInventoryLocations}" 
              SelectedItem="{Binding CurrentLocation, Mode=TwoWay}">

The Bin Number and Quantity text boxes are also data-bound to the CurrentLocation property, as shown by the following code.

<Button Content="Save" Height="23" HorizontalAlignment="Left" Margin="271,192,0,0"  
        Name="saveButton" VerticalAlignment="Top" Width="83" 
        Click="saveButton_Click" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="84,147,0,0" 
         Name="binTextBox" VerticalAlignment="Top" 
         DataContext="{Binding CurrentLocation}" Width="120" 
         Text="{Binding BinNumber, Mode=TwoWay}" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="268,147,0,0" 
         Name="quantityTextBox" DataContext="{Binding CurrentLocation}"  
         VerticalAlignment="Top" Width="86" 
         Text="{Binding Quantity, Mode=TwoWay, ValidatesOnExceptions=True}" />

If the user elects to add a new inventory location, the ViewModel simply sets the value of the CurrentLocation property to a new InventoryLocationItem instance. It also tracks the new instance in the private newLocation field. Two-way data binding ensures that the corresponding controls in the UI are cleared.

public void SetNewLocation()
{
  if (newLocation == null)
  {
    newLocation = new InventoryLocationsItem();
  }
  else
  {
    newLocation.BinNumber = string.Empty;
    newLocation.Quantity = null;
  }

  CurrentLocation = newLocation;
}

When the user clicks the Save button, the code-behind for the PartsLocation control calls the InventoryLocationSaved method in the ViewModel.

private void saveButton_Click(object sender, RoutedEventArgs e)
{
  PartInventoryViewModel viewModel = (PartInventoryViewModel)this.DataContext;

  string error = viewModel.InventoryLocationSaved();
  if(error != null)
    MessageBox.Show(error);
}

This method simply forwards the command to the ViewModel by calling the InventoryLocationSaved method. Thanks to the two-way data binding in the control XAML, the new or updated values are already available in the ViewModel. The objects that populate the observable collections are the same objects that populate the data context, so the ViewModel can use a call to context.UpdateObject to let the Model know that an item has changed. To commit the changes in the model, the ViewModel calls the context.BeginSaveChanges method.

public string InventoryLocationSaved()
{
  string error =  ValidateSaveInputs(currentLocation.BinNumber, 
                                     currentLocation.Quantity);
  if (error == null)
  {
    if (CurrentLocation != newLocation)
    {
      //CurrentLocation represents an object that is already in the data context    
      //and observable collection.  We just need to update the values.
      context.UpdateObject(CurrentLocation);
    }
    else
    {
      newLocation.PartId = CurrentPart.Id;

      //Add the new part to the data context.
      context.AddToInventoryLocations(newLocation);

      //Add the new part to the observable collection.
      this.currentInventoryLocations.Add(newLocation);

      this.CurrentLocation = newLocation;
      newLocation = null;
    }

    context.BeginSaveChanges(SaveChangesOptions.Batch, OnSaveChanges, null);
  }
  return error;
}

The callback method simply displays a message indicating that the updates were successfully applied.

private void OnSaveChanges(IAsyncResult result)
{
    Deployment.Current.Dispatcher.BeginInvoke(() =>
    {
        context.EndSaveChanges(result);
        MessageBox.Show("Inventory Changes Saved Successfully");
    });
}

At this point, you've seen how each leg of the MVVM pattern works in this implementation. In this case the model does not generate change events, so that aspect of the pattern is omitted.

Complete MVVM implementation for the Silverlight REST Alt interface

Ff798384.ffb3d832-63b7-450d-a39f-e9c1081a70b4(en-us,PandP.10).png

Data Binding to the Model

The one point that may be unclear in all of this is exactly how the changes happen. The same entity object instances are being used in a number of different cases, which may feel unnatural to Web developers who are used to stateless user interfaces driven by requests and responses. In this case, the data context object first retrieves the entities by querying the SharePoint server. These entities are then added to observable collections to enable tight data binding to the View. This is a common approach in MVVM implementations—entities from the data model can be bound directly to the View, providing that they don’t need to undergo any transformations to match the View. The entity instances added to the observable collections are the same entity instances in the data context. When a user selects an item, such as an inventory location, the CurrentLocation property is set by two-way data binding, and the entity instance that is in the CurrentLocation is also in the observable collection and the data context. As a result, if you update the inventory location in the user interface, the two-way data binding updates the object in the CurrentLocation property, which in turn means that the object in the observable collection and the data context is updated. The code simply needs to call context.UpdateObject to let the data context know that the value has changed. This all works because the same entity instances that were initially retrieved from the SharePoint server remain in memory and are used throughout the application.

Additional Considerations

The Silverlight REST Alt interface is a relatively simple implementation of the MVVM pattern in order to provide a straightforward demonstration. Two additional areas that are not covered in depth are commands and validation.

Commands are used to represent actions that require more sophisticated coordination between the View, the ViewModel, and the Model. Commands can be implemented as methods in the ViewModel class, or encapsulated in separate classes that implement the ICommand interface. In both approaches, the ViewModel exposes the command to the View, so that the View can invoke the command in response to user interactions. If the command is implemented as a method on the ViewModel class, you can invoke the command in the code-behind class for the View. For example, the View class in this implementation calls the PartInventoryViewModel.GetParts method when the user clicks the part search button. If the command is implemented as an ICommand instance, the View can bind directly to it, removing the need for any code in the View’s code-behind. Commands can also be directed from the ViewModel to the View. For example, the ViewModel can disable a button on the View if the data provided by the user is not valid.

The ViewModel and the Model validate the data that they encapsulate. Data validation is fully integrated into the WPF and Silverlight data-binding mechanism. This enables the ViewModel or the Model to validate data as the user updates it in the View, and enables the View to automatically inform the user that invalid data has been entered. The Client RI includes only minimal validation.

For more detailed insights into the MVVM pattern, see WPF Apps With The Model-View-ViewModel Design Pattern and Introduction to Model/View/ViewModel pattern for building WPF apps on MSDN.