MVVM Theory

At the heart of the MVVM design pattern is the separation of a view's logic from its look. Instead of having a mass of code in a View's code-behind, much of this code will be extracted out into a ViewModel class—separated from the View itself. This ViewModel class will then expose data and operations publicly for one or more Views to consume. The result of this will be that the view should have very little code-behind. You can then use data bindings and commands/behaviors as the glue to connect the View and the ViewModel together.

Let's take a look at the theory behind the MVVM design pattern and how you can put these principles in place in your Silverlight business applications.

The Layers

The MVVM design pattern consists of three core layers:

  • Model
  • View
  • ViewModel

You could think of the pattern as being quite similar to the traditional three-tier pattern, which consists of a presentation tier, business logic tier, and data tier. Although they are quite different patterns and serve different needs, we can compare the layers in the MVVM design pattern to the tiers in the three-tier pattern in the following respects:

  • The View layer corresponds to the presentation tier.
  • The Model layer corresponds to the data tier.
  • The ViewModel layer corresponds to the business logic tier.

However, all of the layers forming the MVVM design pattern actually exist within the presentation tier of the three-tier pattern (the Silverlight application acts as the entire tier), with each layer focused on serving the needs of the user interface. Figure 13-1 demonstrates how the layers within the MVVM design pattern relate to each other, with the arrows representing a “knows about” or “communicates with” interaction.

images

Figure 13–1. The strict relationships between the View, ViewModel, and Model layers

As Figure 13–1 shows, outside of its own layer, the View should technically know only about the ViewModel, and the ViewModel should know only about the Model(s). In practice, however, this is rarely the case, with the ViewModel often presenting the Model(s) to a View to consume directly; therefore, a View will typically have full knowledge of the structure of the Model(s), as demonstrated in Figure 13–2.

images

Figure 13–2. The more common relationships between the View, ViewModel, and Model layers

Let's now take a closer look at the purpose of each of these layers.

Views

You are already well versed in the purpose of Views by now. A View consists of both a XAML file and a code-behind class. Throughout this book, we have made extensive use of the navigation framework, and in this context, both the XAML file and the class inherit from Page (discussed in Chapter 3). How you choose to display Views within your application is of no consequence and has no impact on the implementation of the MVVM design pattern. You may choose use Prism, or perhaps no framework at all to display your Views.

images Note For the purpose of the examples in this chapter, I will assume you are using the navigation framework for structuring and composing your user interface.

In terms of the MVVM design pattern, think of views simply as consumers of data and operations provided by the ViewModel class. Theoretically, when using MVVM, the view's code-behind class should be considered essentially irrelevant, and little to no code should be written in it. Instead, the view's logic and behavior should be placed in the ViewModel class, leaving the code-behind class with just its barebones requirements, a goal often referred to as “zero code-behind.” However, zero code-behind is not a requirement of the MVVM pattern and is often an unrealistic goal. Some tasks are simply better performed in the code-behind—especially when those tasks require directly interacting with the view. Attempting to move these types of tasks into the ViewModel class will lead to increasing the complexity of your code and decreasing its maintainability, which all in all goes against the ultimate goal and primary focus of implementing the MVVM pattern.

images Note When designing views in accordance to the MVVM design pattern, your focus should be on minimizing the use of code-behind but not on removing it all completely.

Models

A model is an object, usually containing data obtained from the server, that the view can display. When you are making use of RIA Services to communicate with the server, your model objects will be the entities returned by a domain service.

ViewModels

Although the view and the model both leverage existing components that you are already familiar with, the ViewModel is an entirely new piece to the puzzle. ViewModels are used to maintain the state of a view. In this, ViewModels have a dual purpose: to expose data and operations to the view and to handle the view's logic and behavior.

images Note For the purposes of this explanation, we will focus on the case where each view has a single corresponding ViewModel (i.e., a one-to-one relationship). However, views and ViewModels can exist in multiple configurations: a ViewModel may have multiple views, or model objects may be wrapped in ViewModels before being exposed to the view. These configurations will be discussed further later in this chapter.

Although its name may indicate that the purpose of a ViewModel is to provide a view of a model, abstracting the model for consumption by the view, it's really the other way around. The real purpose of a ViewModel is to maintain the state of the view and, therefore, should be considered a model of the view. The view's state will be maintained within the ViewModel as data, exposed as properties, and operations, exposed as either methods or commands. The view can then bind to this ViewModel to consume the data and operations that it exposes, and you can then easily write unit tests around the ViewModel.

images Note Good ViewModel design dictates that a ViewModel should expose state-related data, rather than view-specific data—both naming-wise and data type–wise. For example, instead of exposing a property named SaveButtonEnabled, a better name would be the more generic CanSave. As another example, say you have a details panel on your view that you want displayed only once the data has been retrieved from the server. Instead of exposing a property named DetailsPanelVisibility of type Visibility from your ViewModel, expose a property named AreDetailsLoaded of type boolean. The details panel in the view can then bind its Visibility property to the AreDetailsLoaded property on the ViewModel, using a value converter to convert the value from a boolean to a Visibility enumeration value. It may seem like more work, but following a less view-specific naming and data type strategy enables the ViewModel to be much more generic and reusable.

Connecting the Layers

We now have three independent layers, with a reasonably clean boundary defined between each. Let's take a look now at how the layers can be connected together and communicate with one another.

Connecting the Models to the ViewModels

There's nothing special about the way that the model objects are connected to the ViewModel. Often, the ViewModel will hand off the task of retrieving data from the server to a service agent that will return them to the ViewModel when it's done. The ViewModel can then retain a reference to these models and expose them to the view.

Let's consider the scenario where you are using RIA Services to retrieve data from the server and use that data to populate the view. As discussed in Chapter 5, you can use two methods to communicate with the domain services on the server: the DomainDataSource control or the domain context object directly. The DomainDataSource control is designed to be used declaratively in XAML and thus would completely bypass the ViewModel, making it unsuitable for use in conjunction with the MVVM design pattern. However, the ViewModel class can request data from the server using a domain context and then expose the data it returns to the view.

images Note When retrieving data from the server using RIA Services in conjunction with the MVVM design pattern, you will usually make use of the DomainCollectionView collection view (discussed in Chapter 6) when querying and exposing data from your ViewModel.

Connecting the ViewModel to the View

To connect a ViewModel to a view, you generally assign an instance of the ViewModel to the view's DataContext property. This enables properties of the controls in the view to bind directly to properties on the ViewModel, or the models that it exposes.

There are numerous approaches to instantiating a ViewModel object and assigning it to a view's DataContext property, including these:

  • Instantiating the ViewModel directly in the view's XAML
  • Instantiating the ViewModel directly in the view's code-behind
  • Instantiating the ViewModel elsewhere and passing it as a parameter to the view's constructor
  • Using a ViewModel locator (a common utility included with various MVVM frameworks that follows the service locator pattern)

For example, you can instantiate a ViewModel in XAML and assign it to the DataContext property of the view like so:

<navigation:Page x:Class="Chapter13Sample.Views.LoginView"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:navigation="clr-namespace:System.Windows.Controls; images
               assembly=System.Windows.Controls.Navigation"
      xmlns:vm="clr-namespace:Chapter13Sample.ViewModels"
      mc:Ignorable="d" Title="Login">

  <navigation:Page.DataContext>
    <vm:LoginViewModel />
  </navigation:Page.DataContext>
  
  <Grid x:Name="LayoutRoot">
    <!-- Removed for the purpose of brevity -->
  </Grid>
</navigation:Page>

images Note To instantiate a ViewModel in XAML like this, the XAML parser requires the ViewModel to have a para-meterless constructor. If this is not possible (for example, the view needs to pass the ViewModel the ID of an entity to load), you will have to instantiate it from the code-behind instead.

The previous XAML is the equivalent of the following line of code:

this.DataContext = new LoginViewModel();

Consuming a ViewModel's Data and Operations in a View

Connecting a view to its ViewModel is only a small part of the story. Once a view is bound to a ViewModel, it needs the ability to consume the data and operations that the ViewModel exposes. Let's look at how it can do this now.

Data

If the ViewModel exposes data as public properties, the view can then use data bindings to bind to those properties and consume the data. This effectively provides a two-way connection, bridging the boundary between the view and the ViewModel. The view can consume the data exposed by the ViewModel and update the ViewModel again with any changes that the user makes to the data in the view, via the two-way data binding connection demonstrated in Figure 13–3.

images

Figure 13–3. The interaction between the view and the ViewModel using bindings

images Note When you bind the control properties to the ViewModel's properties, the bindings also listen for the PropertyChanged and the ErrorsChanged events raised by the ViewModel, assuming that the ViewModel implements the INotifyPropertyChanged and INotifyDataErrorInfo interfaces. This means that the view will be aware of ViewModel property values changing and of validation errors and will be able to respond accordingly. When list controls, such as the ListBox, DataGrid, and ComboBox controls, are bound to a collection that implements the ICollectionChanged interface (such as the ObservableCollection<T> type), those controls will automatically update themselves when items are added to or removed from the collection. Hence, Silverlight's binding engine provides additional, hidden interaction between the layers, which the MVVM design pattern takes advantage of to keep a clean separation of concerns between the view and the ViewModel.

Operations

Operations that expose logic and behavior from a ViewModel to a view can be implemented as either public methods or as commands. For example, say you want the user to be able to click a Save button in the view and have the view call the ViewModel to handle the save logic. You might choose to expose the save operation as either a save command that the view can bind to and execute or as a save method that an action/behavior can call, as shown in Figure 13–4.

Let's look at these approaches and compare them.

images

Figure 13–4. The interactions of the View invoking an operation on a ViewModel

Commands

As discussed in Chapter 11, commands are used to encapsulate a piece of logic, which various Silverlight controls can bind to and execute in response to an event, such as a button being clicked. You can expose a command from a ViewModel as a property and bind the Command property of a control in the View to it. When the control is clicked, the command will be executed.

Methods

Methods are the general means of exposing operations from a class according to object-oriented design principles. Methods can be called from XAML using an action/behavior (discussed in Chapter 10). Actions/behaviors aren't native functionality available in Silverlight but are available using the Expression Blend Interactivity library.You can leverage the CallMethodAction action from this library to call a method on the ViewModel in response to a control's event. That is, when something happens in the user interface, the CallMethodAction action can call a method in the ViewModel.

Method vs. Commands

Whether you use methods or commands to expose operations on a ViewModel comes down to personal taste. A command can encapsulate a piece of logic and decouple it from your ViewModel so that it can be reused, but commands do require more code to implement than methods. Methods are an easy way of exposing operations from a ViewModel, but you need to use an action or a behavior, such as the CallMethodAction action, to invoke them from XAML—adding additional XAML and complexity to your view. That said, this is not usually an issue if you use Expression Blend to wire your view and ViewModel up, because it provides a point-and-click approach for applying actions and behaviors to controls. Unfortunately, however, Visual Studio doesn't have the same ability built in, and the action/ behavior XAML will need to be hand coded. Ultimately, both methods are equally acceptable and can be mixed and matched according to your needs.

Notifying the View of an Event from a ViewModel

Looking at the other side of the coin now, how does the ViewModel notify the view that something has happened that it might want to respond to? Silverlight's binding engine is intelligent enough to respond to changes in bound data from the ViewModel (property value changes, validation errors, collection changes, etc.), but what if you need the view to respond to a notification from the ViewModel, such as when it raises an event? This is particularly important in Silverlight, where all calls to the server are asynchronous. For example, perhaps the view needs to navigate to another view once a save operation is complete. We can implement this behavior by raising an event from the ViewModel that the view can listen for by doing either of the following:

  • Wiring up an event handler in the view's code-behind and writing some code to respond to the event
  • Implementing a trigger in the view that listens for the event and responds accordingly, as shown in Figure 13–5
images

Figure 13–5. The interaction of the View handling notifications from the ViewModel

Layer Interaction Summary

Now that we've discussed the core concepts of the MVVM pattern, let's put them together in a single diagram, shown in Figure 13–6, so that you can get a complete overall perspective on how the layers can communicate with one another.

images

Figure 13–6. An overview of the interactions between the MVVM layers

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset