Estimated Time: 45 minutes
The Model-View-ViewModel (MVVM) pattern provides a flexible way to build Silverlight applications that promotes code re-use, simplifies maintenance and supports testing. The pattern consists of three different components including the Model (entities and code on the server), the View (the Silverlight screens) and the ViewModel (the glue between the Model and the View). An example of the how the Model, View and ViewModel components relate to each other is shown next:
In this lab exercise you'll learn how to migrate an existing Silverlight application that uses code-behind files for all of the C# or VB code into a more structured architecture that follows the MVVM pattern. Topics covered include creating a service agent class to handle calls to a WCF service, creating a ViewModelBase class, creating a custom ViewModel class and binding a ViewModel to a View declaratively in XAML. You'll also see how commanding can be used to wire-up events in a View to methods in a ViewModel. The application that you'll work with in the lab exercises is shown next:
It's recommended that you complete the data binding and WCF labs before starting this lab. Some of the steps in the lab will provide the code to use while others will explain the task to perform and let you figure out the code that should be added. If you need help with any of the steps refer to the lab's Completed folder which contains the finished code.
In this exercise you'll remove existing code in a code-beside file and create a service agent class. The service agent class will be used to make calls to a WCF service from within a ViewModel class created later in the lab.
| Language | Lab Files Location |
|---|---|
| C# | /MVVMPattern/Starting Point/C#/CustomerViewer.sln |
| Visual Basic | /MVVMPattern/Starting Point/VB/CustomerViewer.sln |
All of the functionality in the code-beside file will be moved into a ViewModel class that you'll create later in this lab.
Resolve any missing namespaces by right-clicking on an unknown type and then selecting Resolve in the menu.
public interface ICustomersServiceAgent
{
void GetCustomers(EventHandler<GetCustomersCompletedEventArgs> callback);
void SaveCustomer(Customer cust,
EventHandler<SaveCustomerCompletedEventArgs> callback);
}
Public Interface ICustomersServiceAgent
Sub GetCustomers(ByVal callback As _
EventHandler(Of GetCustomersCompletedEventArgs))
Sub SaveCustomer(ByVal cust As Customer, _
ByVal callback As EventHandler(Of SaveCustomerCompletedEventArgs))
End Interface
CustomerServiceClient _Proxy = new CustomerServiceClient();
Dim _Proxy as New CustomerServiceClient()
The CustomerServiceClient class is a WCF service proxy class that will be used to communicate with the remote service to retrieve customer data and perform update and delete operations.
_Proxy.GetCustomersCompleted += callback; _Proxy.GetCustomersAsync();
AddHandler _Proxy.GetCustomersCompleted, callback _Proxy.GetCustomersAsync()
_Proxy.SaveCustomerCompleted += callback; _Proxy.SaveCustomerAsync(cust);
AddHandler _Proxy.SaveCustomerCompleted, callback _Proxy.SaveCustomerAsync(cust)
In this exercise you'll create a ViewModelBase class that provides core functionality that can be used by one or more ViewModel classes. You'll then derive from ViewModelBase and create a ViewModel class that will be used to retrieve and manipulate customer data used in the MainPage.xaml View.
| Language | Lab Files Location |
|---|---|
| C# | Documents\Visual Studio 2010\Code Snippets\Visual C#\My Code Snippets |
| Visual Basic | Documents\Visual Studio 2010\Code Snippets\Visual Basic\My Code Snippets |
The mvvmInpc snippet will be used to create properties within a ViewModel class later in this lab. It's one of several snippets available in MVVM Light (http://mvvmlight.codeplex.com) and has been modified for use in this lab exercise.
INotifyPropertyChanged is an important interface in Silverlight used by the data binding engine to notify controls and other objects when a bound property value changes. By implementing INotifyPropertyChanged on the ViewModelBase class you can write the code once and re-use it across multiple ViewModel classes. The interface is located in the System.ComponentModel namespace.
If you need help with this step please refer to the ViewModelBase class in the lab's Completed folder. Creating an OnPropertyChanged method was covered in the data binding lab.
When using the MVVM pattern and ViewModel classes it's important to know when code is executing in a design-time tool such as Visual Studio or Expression Blend and when it's executing at runtime. When code runs in a design tool network calls will not execute properly and can error out the designer. The IsDesignTime property will be used to detect where code is running to ensure that ViewModel classes execute properly at design-time.
| Property Name | Property Type |
|---|---|
| Customers | ObservableCollection of Customer |
| CurrentCustomer | Customer |
| StatusMessage | string |
If you need help with creating the properties in this step refer to the MainPageViewModel class in the lab's Completed folder.
The ObjectState parameter used in the following code is automatically created by the WCF proxy generator when using self-tracking entities in Entity Framework 4. By changing the object state we can easily track whether delete or update operations should occur for a given Customer object.
private void GetCustomers()
{
ServiceAgent.GetCustomers((s, e) => Customers = e.Result);
}
public void UpdateCustomer()
{
SaveCustomer(ObjectState.Modified);
}
public void DeleteCustomer()
{
SaveCustomer(ObjectState.Deleted);
Customers.Remove(CurrentCustomer);
CurrentCustomer = null;
}
private void SaveCustomer(ObjectState state)
{
CurrentCustomer.ChangeTracker.State = state;
ServiceAgent.SaveCustomer(CurrentCustomer, (s, e) =>
{
StatusMessage = (e.Result.Status) ? "Success!" :
"Unable to complete operation";
});
}
Private Sub GetCustomers()
ServiceAgent.GetCustomers(Sub(s, e) Customers = e.Result)
End Sub
Public Sub UpdateCustomer()
SaveCustomer(ObjectState.Modified)
End Sub
Public Sub DeleteCustomer()
SaveCustomer(ObjectState.Deleted)
Customers.Remove(CurrentCustomer)
CurrentCustomer = Nothing
End Sub
Private Sub SaveCustomer(ByVal state As ObjectState)
CurrentCustomer.ChangeTracker.State = state
ServiceAgent.SaveCustomer(CurrentCustomer, _
Sub(s, e) StatusMessage = _
If(e.Result.Status, "Success!", "Unable to complete operation"))
End Sub
public MainPageViewModel() : this(new CustomersServiceAgent())
{
}
public MainPageViewModel(ICustomersServiceAgent serviceAgent)
{
if (!IsDesignTime)
{
if (serviceAgent != null) ServiceAgent = serviceAgent;
GetCustomers();
}
}
Public Sub New()
Me.New(New CustomersServiceAgent())
End Sub
Public Sub New(ByVal serviceAgent As ICustomersServiceAgent)
If Not IsDesignTime Then
If serviceAgent IsNot Nothing Then
Me.ServiceAgent = serviceAgent
End If
GetCustomers()
End If
End Sub
The first constructor will be called if the ViewModel is instantiated without any parameters. It passes a new instance of the CustomersServiceAgent object to the second constructor which assigns it to the ServiceAgent property when not in design mode (note that instead of hard coding the service agent type it could be injected using a dependency injection framework). The second constructor accepts any object that implements the ICustomersServiceAgent interface allowing mock objects to be passed in for the service agent when testing the ViewModel class. Following this pattern provides a flexible way to work with different types of service agent objects when the application is running as well as when tests need to be executed.
In this exercise you'll add commanding support into the SilverlightCustomerViewer project. Commanding allows events triggered in a View such as a Button's Click event to be routed directly to a ViewModel instance without having to add code in a code-beside file. You'll be introduced to the ICommand interface as well as a RelayCommand class that will be used within MainPageViewModel to handle commands. Finally, you'll bind MainPageViewModel to a View using a declarative binding syntax.
Commanding is the process of forwarding events that occur in the user interface to a ViewModel object for processing at runtime. The RelayCommand class will be used to wire properties in a ViewModel to methods that are invoked when any control derived from ButtonBase is clicked in a View. It satisfies the ICommand interface allowing commanding to be used in Silverlight MVVM applications.
public RelayCommand UpdateCustomerCommand
{
get;
private set;
}
public RelayCommand DeleteCustomerCommand
{
get;
private set;
}
Private _UpdateCustomerCommand As RelayCommand
Public Property UpdateCustomerCommand() As RelayCommand
Get
Return _UpdateCustomerCommand
End Get
Private Set(ByVal value As RelayCommand)
_UpdateCustomerCommand = value
End Set
End Property
Private _DeleteCustomerCommand As RelayCommand
Public Property DeleteCustomerCommand() As RelayCommand
Get
Return _DeleteCustomerCommand
End Get
Private Set(ByVal value As RelayCommand)
_DeleteCustomerCommand = value
End Set
End Property
private void WireCommands()
{
UpdateCustomerCommand = new RelayCommand(UpdateCustomer);
DeleteCustomerCommand = new RelayCommand(DeleteCustomer);
}
Private Sub WireCommands()
UpdateCustomerCommand = New RelayCommand(AddressOf UpdateCustomer)
DeleteCustomerCommand = New RelayCommand(AddressOf DeleteCustomer)
End Sub
WireCommands handles associating two RelayCommand properties with methods that will be invoked as a command is executed in a View. For example, any Button with a Command property bound to UpdateCustomerCommand will cause the UpdateCustomer method to be invoked.
xmlns:viewModels="clr-namespace:SilverlightCustomerViewer.ViewModels"
<viewModels:MainPageViewModel x:Key="ViewModel" />
DataContext="{Binding Source={StaticResource ViewModel}}"
You'll need to click on the icon to the right of SelectedItem and then select Apply Data Binding to access the data binding window.
To do this, first clear any existing bindings by selecting the data icon to the right of the Text property and select Reset Value. You'll need to bind the Text property of each TextBox to CurrentCustomer and then select the appropriate property from CurrentCustomer (FirstName, LastName, etc.) in the data binding window.
In this exercise you converted the SilverlightCustomerViewer application to use the Model-View-ViewModel pattern. Tasks completed include creating a service agent class for communication with a WCF service as well as a ViewModel class that is responsible for retrieving data and storing it in properties. You also added support for commanding and bound a ViewModel class to a XAML view. Requirements satisfied in this lab include:
Comments (0)