Published articles on other web sites*

Published articles on other web sites*

Using the MVVM Pattern in Silverlight Applications

Using the MVVM Pattern in Silverlight Applications

By Microsoft Silverlight Team|December 1, 2010|Level 300 : Intermediate

Contents

Overview

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.

You Will Benefit from this Lab if:

  • You're interested in building Silverlight applications that take advantage of code re-use, promote testability and reduce maintenance costs
  • You'd like to separate your Silverlight application into distinct modules and provide a pattern that can be followed by a team of developers
  • You're interested in learning the MVVM pattern and the benefits it offers

You Will Learn:

  • How to convert an existing Silverlight application to use the MVVM pattern
  • The role of the Model, ViewModel and View in the MVVM pattern
  • How to create a service agent class and the role it can play in an application to promote code re-use
  • How to create a ViewModel class and handle property change notifications
  • How to bind a ViewModel class to a View
  • How to communicate between a View and ViewModel using commanding

Business Requirements for the Silverlight application include:

  • Remove existing code from a Silverlight code-beside file
  • Create a service agent interface and class to handle calls to a WCF service
  • Create a ViewModelBase class to handle INotifyPropertyChanged notifications
  • Create a ViewModel class that derives from ViewModelBase and handles data interactions
  • Add support for commanding into a ViewModel class
  • Bind a ViewModel class to a View in XAML
  • Bind Silverlight controls to ViewModel properties

Exercise 1: Creating a Service Agent Class

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.
  1. Open the following Visual Studio 2010 solution file based upon your chosen language.
    Language Lab Files Location
    C# /MVVMPattern/Starting Point/C#/CustomerViewer.sln
    Visual Basic /MVVMPattern/Starting Point/VB/CustomerViewer.sln
  2. The following projects are available in the solution:
    • CustomerService.Model – Contains entities and data repository classes used to access an AdventureWorks LT database.
    • CustomersService – A WCF service application that exposes entities to various applications.
    • SilverlightCustomerViewer – A Silverlight project that consumes data from a WCF service.
    • SilverlightCustomerViewer.Web – A website project used to host the SilverlightCustomerViewer application.
  3. Right-click CustomerService.svc in the CustomersService project and select View in Browser from the menu. This will start a local WCF server and show details about the service.
  4. Back in Visual Studio, right-click SilverlightCustomerViewerTestPage.html in the SilverlightCustomerViewer.Web project and select View in Browser from the menu. Select customers from the ComboBox control (note that you may have to wait a moment for the customers to load the first time the application is run) and notice that the data for each customer displays in the details area.
  5. Back in Visual Studio, open MainPage.xaml.cs located in the SilverlightCustomerViewer project in the code editor and remove all of the existing code within the MainPage class except the constructor and the call to InitializeComponent.
    All of the functionality in the code-beside file will be moved into a ViewModel class that you'll create later in this lab.
  6. Open MainPage.xaml and remove the Click event handlers defined on the two Button controls.
  7. Right-click on the SilverlightCustomerViewer project and select Add ? New Folder. Give the folder a name of ServiceAgents.
  8. Add a new class into the ServiceAgents folder named CustomersServiceAgent.
  9. Add the following interface immediately above the class and resolve any missing namespaces:
    Resolve any missing namespaces by right-clicking on an unknown type and then selecting Resolve in the menu.

    C#

    public interface ICustomersServiceAgent
    {
       void GetCustomers(EventHandler<GetCustomersCompletedEventArgs> callback);
       void SaveCustomer(Customer cust,
         EventHandler<SaveCustomerCompletedEventArgs> callback);
    }

    Visual Basic

    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
  10. Implement the ICustomerService interface on the CustomersServiceAgent class.
  11. Add the following field into the CustomersServiceAgent class:

    C#

    CustomerServiceClient _Proxy = new CustomerServiceClient();

    Visual Basic

    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.
  12. Add the following code in the GetCustomers method to handle calling the WCF service operation:

    C#

    _Proxy.GetCustomersCompleted += callback;
    _Proxy.GetCustomersAsync();

    Visual Basic

    AddHandler _Proxy.GetCustomersCompleted, callback
    _Proxy.GetCustomersAsync()
  13. Add the following code in the SaveCustomer method to handle communicating delete and update data operations to the WCF service:

    C#

    _Proxy.SaveCustomerCompleted += callback;
    _Proxy.SaveCustomerAsync(cust);

    Visual Basic

    AddHandler _Proxy.SaveCustomerCompleted, callback
    _Proxy.SaveCustomerAsync(cust)

Exercise 2: Creating a ViewModel Class

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.
  1. Locate the file named mvvmInpcPropertyCS.snippet (for C#) or mvvmInpcPropertyVB.snippet (for VB) in the SilverlightCustomerViewer/CodeSnippets folder.
  2. Copy the file for your chosen language to the following location (or use Tools ? Code Snippets Manager in Visual Studio to import it):
    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.
  3. Right-click on the SilverlightCustomerViewer project and select Add ? New Folder. Give the folder a name of ViewModels.
  4. Add a new class into the ViewModels folder named ViewModelBase and mark it as abstract (C#) or MustInherit (VB).
  5. Implement the INotifyPropertyChanged interface on the class and resolve the namespace.
    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.
  6. Once the interface has been implemented on the ViewModelBase class, add a method named OnPropertyChanged into ViewModelBase to handle raising the PropertyChanged event from the INotifyPropertyChanged interface.
    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.
  7. Add a new public Boolean property into the ViewModelBase class named IsDesignTime.
  8. Add a get block (but no set block) into the property that returns DesignerProperties.IsInDesignTool as the value. Resolve any namespaces as needed.
    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.
  9. Add a new class into the ViewModels folder named MainPageViewModel that derives from ViewModelBase .
  10. Add the following properties into MainPageViewModel using the mvvmInpc code snippet and resolve any missing namespaces as necessary (type mvvmInpc + tab + tab to use the snippet in C# or mvvminpc + tab to use the snippet in VB):
    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.
  11. Within the CurrentCustomer property's set block add code to set the StatusMessage property to empty strings (add the code within the existing "if" statement).
  12. Add another property named ServiceAgent of type ICustomersServiceAgent and resolve the namespace (create it as a standard .NET property).
  13. Add the following methods into MainPageViewModel to handle retrieving, updating and deleting customer objects:
    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.

    C#

    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";
        });
    }

    Visual Basic

    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
  14. Add the following constructors into MainPageViewModel:

    C#

    public MainPageViewModel() : this(new CustomersServiceAgent())
    {
    }
     public MainPageViewModel(ICustomersServiceAgent serviceAgent)
    {
        if (!IsDesignTime)
        {
            if (serviceAgent != null) ServiceAgent = serviceAgent;
            GetCustomers();
    
        }
    }

    Visual Basic

    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.
  15. Ensure that the SilverlightCustomerViewer project compiles before proceeding to the next exercise.

Exercise 3: Handling Commands and Binding to a ViewModel

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.
  1. Open the Commanding/RelayCommand.cs class in the SilverlightCustomerViewer project and note that it implements ICommand. This interface is required in order for a Silverlight application to support commanding.
    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.
  2. Add the following code into MainPageViewModel to create properties capable of binding to controls that expose a Command property. Resolve the required namespace.

    C#

    public RelayCommand UpdateCustomerCommand
    {
        get;
        private set;
    }
     public RelayCommand DeleteCustomerCommand
    {
        get;
        private set;
    }

    Visual Basic

    Private _UpdateCustomerCommand As RelayCommandPublic Property UpdateCustomerCommand() As RelayCommand
        Get
            Return _UpdateCustomerCommand
        End Get
        Private Set(ByVal value As RelayCommand)
            _UpdateCustomerCommand = value
        End Set
    End Property
    Private _DeleteCustomerCommand As RelayCommandPublic Property DeleteCustomerCommand() As RelayCommand
        Get
            Return _DeleteCustomerCommand
        End Get
        Private Set(ByVal value As RelayCommand)
            _DeleteCustomerCommand = value
        End Set
    End Property
  3. Add the following code into MainPageViewModel to handle wiring up the properties defined in the previous step to methods that will be invoked once a command is executed:

    C#

    private void WireCommands()
    {
        UpdateCustomerCommand = new RelayCommand(UpdateCustomer);
        DeleteCustomerCommand = new RelayCommand(DeleteCustomer);
    }

    Visual Basic

    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.
  4. Add a call to the WireCommands method within MainPageViewModel's second constructor (add the code immediately under the GetCustomers method call within the constructor).
  5. Add code within the CurrentCustomer property's set block to assign the IsEnabled property of UpdateCustomerCommand and DeleteCustomerCommand to a value of true (add the code within the existing "if" statement).
  6. Open MainPage.xaml and add the following xml namespace definition on the UserControl element to reference the ViewModels namespace:

    XAML

    xmlns:viewModels="clr-namespace:SilverlightCustomerViewer.ViewModels"
  7. Create a UserControl.Resources element within the XAML immediately below the beginning UserControl element and add the following code into it:

    XAML

    <viewModels:MainPageViewModel x:Key="ViewModel" />
  8. Bind the ViewModel key created in the previous step to the LayoutRoot element's DataContext property using a StaticResource binding. This will bind a MainPageViewModel object instance to the DataContext.

    XAML

    DataContext="{Binding Source={StaticResource ViewModel}}"
  9. Build the solution. If you encounter any compilation errors resolve them before continuing.
  10. Right-click on the ComboBox control in the designer and select Properties from the menu.
  11. Locate the ItemsSource property and click on its Binding value to view the data binding window. Select Customers from the available Path values as shown next:
  12. Follow the same procedure shown in the previous step to bind the ComboBox control's SelectedItem property to CurrentCustomer.
    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.
  13. Bind the Text property of each TextBox control to the associated property in CurrentCustomer.
    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.
  14. Bind the Command property of each Button control to the appropriate command property in the ViewModel (UpdateCustomerCommand or DeleteCustomerCommand) using the same technique shown in the previous steps.
  15. Drag a TextBlock control from the Toolbox and drop it directly to the right of the existing Delete button. Change the Width of the TextBlock to 200.
  16. Bind the newly added TextBlock control's Text property to the StatusMessage property of the ViewModel using the data binding window.
  17. Run the application and test the functionality. As you update or delete a Customer object a success message should appear to the right of the Button controls. Select a different customer and the success message should disappear.

Summary

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:
  • Remove code located in a Silverlight code-beside file
  • Create a service agent interface and class to handle calls to a WCF service
  • Create a ViewModelBase class to handle INotifyPropertyChanged notifications
  • Create a ViewModel class that derives from ViewModelBase and handles data interactions
  • Add support for commanding into a ViewModel class
  • Bind a ViewModel class to a View in XAML
  • Bind Silverlight controls to ViewModel properties

No hay comentarios:

Publicar un comentario