(X) Hide this
    • Login
    • Join
      • Say No Bots Generate New Image
        By clicking 'Register' you accept the terms of use .

Data Driven Applications with MVVM Part II: Messaging, Unit Testing, and Live Data Sources

(16 votes)
Zoltan Arvai
>
Zoltan Arvai
Joined Jul 20, 2009
Articles:   7
Comments:   5
More Articles
4 comments   /   posted on Jun 16, 2010

This article is compatible with the latest version of Silverlight.

Introduction

In this short series of articles I’ll cover what MVVM is and how to use it in practice, how to solve issues when applying this pattern and how to take advantage from it.

In the previous article we discussed what MVVM is and why it matters. In this article we’ll try to benefit from the pattern and talk about the issues you have to battle through when working with MVVM. Our sample application was able to pull some data from a model, and display it on the screen in a master-detail fashion.

Now what we want is to add extra functionalities to our application, like being able to remove an item by clicking on a remove button, located next to each item in the ListBox.

Adding Remove functionality – Messaging

So here is the deal. I want to place a button displaying an X, next to each of the books in the ListBox. When I click on the remove button, the associated Book gets deleted! However this seems a very straightforward task, you’ll see very shortly, that it isn’t. Let’s think this trough a bit.

We want to call a remove operation when we click the remove button. The remove button is in the DataTemplate, its DataContext is a BookDetailsViewModel instance. If I want this functionality, the Remove button’s command property should be bound to a RemoveBookCommand defined in the BookDetailsViewModel class. This means that I’ll have to write a Remove() method in this class. Right now the BookDetailsViewModel class represents the necessary data of a single book. This seems problematic. Why? Because when I remove an item:

  1. I need to persist the changes in the data store.
  2. I need to refresh my ListBox containing all those books!

Let’s see the first. I’m going to need a reference to the DataSource, so I can persist the changes in the data store. I don’t have this reference right now. I can add it though, however it seems a bit strange yet, since removing an item from a list is not the responsibility of that single item. You remember the single responsibility principle? Are you sure this should be controlled by the BookDetailsViewModel class? Yeah, I don’t think so either.

What about the second one? On the BooksView UserControl, I need to refresh the ListBox, which has its ItemsSource property set to the Books property, defined in the BooksViewModel. I don’t have a reference to that ViewModel in my BookDetailsViewModel! I could add it, but then again, the single responsibility principle. Not to mention that it would increase complexity AND here is what really is a problem, it would increase the level of dependency between the ViewModels. However doing  this is not illegal, but it’s definitely not something I would like to do.

Also I could add a third problem, which right now does not concern us, but it could definitely be an issue. Maybe another part of the application, another ViewModel wants to know if an item gets removed by the application. Maybe an other module!

If we want to address this issue, we immediately try to think of some sort of a static or singleton solution. Don’t think too much, we have a solution prepared for us. The MVVM Light Toolkit, which we used in our base application, provides us a Messenger class. This is a singleton object and you can use it to send messages for different subscribers.

From any ViewModel, you can subscribe for a specific type of message. If someone sends that specific type of message, everybody who subscribed for that type of message will receive it. You can send very simple type of messages, like a string, or GenericMessage<T>s. However I prefer to create a custom message class for every single message type / case.

So here is what I want. I will send a message from the BookDetailViewModel, and tell the world that this specific item should be removed, and let the rest of the world worry about it! :)

Let’s add the extras to the BookDetailsViewModel. First let’s create the RemoveBookMessage class:

 namespace MVVMProductsDemo.Messages
 {
     public class RemoveBookMessage : MessageBase
     {
        public BookDetailsViewModel Book { get; set; }
  
        public RemoveBookMessage(BookDetailsViewModel bookToRemove)
        {
            this.Book = bookToRemove;
       }
    }
 }

Now let’s add commanding and message sending to the BookDetailsViewModel class:

 private RelayCommand removeBookCommand;
  
 public RelayCommand RemoveBookCommand
 {
     get { return removeBookCommand; }
 }
  
  
 public void RemoveBook()
 {
     Messenger.Default.Send<RemoveBookMessage>(
         new RemoveBookMessage(this));
 }
   
 //This one goes in the constructor
 removeBookCommand = new RelayCommand(RemoveBook);

Now everybody, who wants to know about the book removal, should subscribe! Let’s change the BooksViewModel’s constructor!

 public BooksViewModel(IBookDataSource bookDataSource)
 {
     this.bookDataSource = bookDataSource;
  
     if (bookDataSource != null)
     {
        bookDataSource.LoadBooksCompleted += new EventHandler<BooksLoadedEventArgs>(bookDataSource_LoadBooksCompleted);
        bookDataSource.RemoveBookCompleted += new EventHandler<OperationEventArgs>(bookDataSource_RemoveBookCompleted);
     }
  
     loadBooksCommand = new RelayCommand(LoadBooks);
  
     Messenger.Default.Register<RemoveBookMessage>(this, RemoveBook);
 }

As you can see the Register() method needed a parameter of type Action<RemoveBookMessage>. This is point to a callback method that runs when a message is received.

 public void RemoveBook(RemoveBookMessage message)
 {
     //Remove item from collection!
     Books.Remove(message.Book);
  
     //Remove item from data store
    bookDataSource.RemoveBook(message.Book.BookID);
 }
   
 void bookDataSource_RemoveBookCompleted(object sender, OperationEventArgs e)
 {
     if (e.Error != null)
     {
         //Do dg.
         return;
     }
 }
So now, our ViewModels are ready. Also the IBookDataSource provides a way to remove the item from the data store. We just need to modify a view. In some scenarios you’d want to defined a separate UserControl as a special view, and use it as a DataTemplate. However that is not necessary. We can just create a simple ItemTemplate here.

Let’s change the ListBox in the BooksView.xaml:

 <ListBox x:Name="lboxBooks" HorizontalAlignment="Left" Margin="8,8,0,29" Width="129" ItemsSource="{Binding Books}" 
              SelectedItem="{Binding SelectedBook, Mode=TwoWay}">
     <ListBox.ItemTemplate>
         <DataTemplate>
             <StackPanel Orientation="Horizontal">
                 <Button Content="X" Command="{Binding RemoveBookCommand}" Width="20"/>
                 <TextBlock Text="{Binding Title}" Margin="8,0,0,0"/>
             </StackPanel>
         </DataTemplate>
     </ListBox.ItemTemplate>          
 </ListBox>

As you can see, by clicking the button, you’ll invoke the RemoveBookCommand, which will invoke the RemoveBook() method, which sends a RemoveBookMessage. On the other side the subscribers for this specific type of message will handle it accordingly. Remove it from the collection and the data store as well. However Messaging is mostly useful when communicating between different modules. So if you use it between different ViewModels, be careful, not to overuse it, since it can lead to a “what the hell is happing right now” situation.

By now we have tons of functionality! I’m getting worried about later changes to this code base. Will my application still work, if I do minor changes? This is where Unit Testing can help you a lot! I told you, MVVM architecture is really testable. So let’s write some unit tests!

Unit testing MVVM

Well Silverlight Unit Test projects are not default in Visual Studio 2010. It’s part of the Silverlight Toolkit. So if you want to do unit testing, you should download it from here. After you install the toolkit, you’ll have a new project type called Silverlight Unit Test Application. The Project Template will add a new page (or web project) for the unit tests. In the Unit Test project you’ll have to reference both the Model and the Demo Project.

There is a couple of things worth mentioning here. Most of the ViewModels work asynchronously, so we have to keep that in mind, when writing tests! In order to support this scenario, we’ll use a special base class for our TestClass, called SilverlightTest. Our TestMethod will be decorated with the AsynchronousAttribute so the framework knows how to handle it. We’ll use our mock data sources (since we don’t use real data sources in unit tests). To handle asynchronous callbacks we’ll use the EnqueueXXX methods. Let’s create a test for the LoadBook operation.

 namespace MVVMProductsDemo.UnitTests
 {
     [TestClass]
     public class Tests : SilverlightTest //Note that the base class is SilverlightTest
    {
         [TestMethod]
         [Asynchronous] //Indicate that this test will include asynchronous operations
         public void LoadBook()
         {
             //Flag to indicate that the async operation is done
             bool isAsyncOperationCompleted = false;
  
             //Use the mock data source
             IBookDataSource dataSource = new MockBookDataSource();
             BooksViewModel viewModel = new BooksViewModel(dataSource);
             
             //If the books are loaded set the completed flag to true!
             dataSource.LoadBooksCompleted += (s,e) => isAsyncOperationCompleted = true;
   
             //Call method
             EnqueueCallback(viewModel.LoadBooks);
              
             //Wait for flag to be true!
             EnqueueConditional(() => isAsyncOperationCompleted);
   
             //If async operations is done, check for the valid conditions!
             EnqueueCallback(() => Assert.IsTrue(viewModel.Books.Count > 0, "Books count is zero!"));
   
             //Indicate that test is complete
             EnqueueTestComplete();
         }
     }
 }

As you can see we created a flag (isAsnycOperationCompleted) to indicate whether the asynchronous operation is completed or not. Then we’ll configure our BooksViewModel to use the MockBookDataSource class. When the LoadBooksCompleted event raises, we should set the flag to true. Now we’ll call the LoadBooks() method using EnqueueCallback, and wait until it completes (see EnqueueConditional). After that we can check whether we have any BookDetailsViewModel instance loaded into the Books property. Finally we have to indicate that the Test is complete by now (EnqueueTestComplete). If we don’t do that, the test will run forever. You can run the test by starting the generated Test Page.

unittest

There you go. We just tested our ViewModel. Of course more extensive testing is necessary to make sure, you release a high quality product! Using this approach you can test the UI as well by instantiating the View UserControls, however I prefer to stick with testing the ViewModels and only do UI testing where it is absolutely necessary.

Changing to Live, WCF-based Data Source

So far we’ve used a mock data source object in our application. It’s time to move to a “real” data source, provided by a WCF Service. Let’s think the basics through. Changing to a live data source should not be that big of a problem. I could write a LiveDataSource class, that works well with a WCF proxy, to communicate with the server side! What about my entities? Right now I have this Book class on the client side. One thing I could do, is adding this Book class to the server side as well. Another approach could be to create an extra transformation layer on the client side, to convert the data received from the server side, to our Book entity. What I’m going to do here, is to create a separate Model project for my entities for the server side, and I’m going to add the Book class file in the silverlight project as a LINK.

I’m going to have to set up it’s default namespace to the same as on the client side. (In this case I have to delete the Web substring.)

namespace
Now I can add the Book class as a link.

addaslink
Now we can reference this project in our Web application / WCF Service project. We can implement our Silverlight-enabled WCF Service. Also I have to add a couple of attributes to my Book class so it can we serialized with WCF. These are the DataContract and DataMember attributes.

 [DataContract(Namespace="MVVMProductsDemo.Model")]
 public class Book
 {
     [DataMember]
     public int BookID { get; set; }
  
     [DataMember]
    public string Title { get; set; }
   
     [DataMember]
     public string Author { get; set; }
  
     [DataMember]
     public decimal Price { get; set; }
 }

Here is the WCF Service implementation:

 [ServiceContract(Namespace = "")]
 [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
 public class BookService
 {
     [OperationContract]
     public IEnumerable<Book> LoadBooks()
     {
         List<Book> bookList = new List<Book>();
  
         bookList.Add(
             new Book
             {
                 BookID = 1,
                 Author = "Tom Clancy",
                 Title = "Rainbow Six",
                 Price = 29.99m
             });
   
         bookList.Add(
             new Book
             {
                 BookID = 2,
                 Author = "Tom Clancy",
                 Title = "Executive Orders",
                  Price = 19.99m
             });
   
         bookList.Add(
             new Book
             {
                  BookID = 3,
                  Author = "John Grisham",
                  Title = "The Partner",
                  Price = 22.99m
             });
   
         return bookList;
     }
 }
The next step is to implement the LiveBookDataSource class and use this WCF Service to get the data. We should not forget, that calls to the WCF service will be asynchronous.

I’m going to need to do a little restructuring. I want to keep my model project for entities only, so I’m going to create a separate project for my data sources and I’m going to move all my data source classes and interfaces to this project. I’m going to reference my model project here and also add a service reference to my WCF Service, and tell the proxy generation tool to use existing types in my referenced assemblies, instead of generating its own Book classes.

wcfconfig

Let’s see the LiveBookDataSource implementation details.

 public class LiveBookDataSource : IBookDataSource
 {
  
     #region IBookDataSource Members
  
     public void LoadBooks()
    {
         BookServiceClient client = new BookServiceClient();
         client.LoadBooksCompleted += new EventHandler<LoadBooksCompletedEventArgs>(client_LoadBooksCompleted);
         client.LoadBooksAsync();
     }
   
     void client_LoadBooksCompleted(object sender, LoadBooksCompletedEventArgs e)
     {
         IEnumerable<Book> books = e.Result;
  
         if (LoadBooksCompleted != null)
         {
             //Error is null if nothing went wrong
             LoadBooksCompleted(this, new BooksLoadedEventArgs(books) { Error = e.Error });
         }
     }
   
     public void RemoveBook(int bookID)
     {
         //Remove book through WCF
     }
  
     public event EventHandler<BooksLoadedEventArgs> LoadBooksCompleted;
  
     public event EventHandler<OperationEventArgs> RemoveBookCompleted;
  
     #endregion
 }

As you can see, LoadBooks() calls a WCF LoadBooksAsync operations. When the operation ends, we get notified through an event, then we pass this data back to our caller, through an event.

Now that we have a live data source, we can change the mock implementation in the Application_Startup method. Also you have to move the ServiceReference.ClientConfig xml file in the DataSources project to the Silverlight application project, since the running application is going to look for those settings there.

 //Change this Line
 IBookDataSource dataSource = new MockBookDataSource();
  
 //To this line
 IBookDataSource dataSource = new LiveBookDataSource();

Now we can run our application. There you go, now we are on live data source!

Summary

What do we have right now? We have learned a new concept called Messaging that helps us communicating between different modules or view models if that’s necessary. Also we created a unit test to make sure that we know if later changes have any kind of effect on our codebase. We learned that Silverlight is special, so we need to write Asynchronous unit tests. The Unit Testing Framework in the Silverlight Toolkit comes to the rescue. Finally we changed our mock data source to a wcf data source and shared entities between the client and the server.

We still need to discuss some issues, like how Validation works,  or how we can respond in the view model to UI specific events, or how states can be managed from the ViewModel.

If you are interested, read Part 3.

Download the source code


Subscribe

Comments

  • -_-

    RE: Data Driven Applications with MVVM Part II: Messaging, Unit Testing, and Live Data Sources


    posted by SDevGuy on Jun 17, 2010 06:36
    good stuff, keep it coming!
  • -_-

    RE: Data Driven Applications with MVVM Part II: Messaging, Unit Testing, and Live Data Sources


    posted by Manish Kesharwani on Aug 11, 2010 15:57
    Great Work Done.
  • -_-

    RE: Data Driven Applications with MVVM Part II: Messaging, Unit Testing, and Live Data Sources


    posted by pillesoft on Oct 25, 2010 19:09

    Hello Zoli,

    thanks for this article, this is quite great, however i have an issue in the last section. there must be some problem with the service configuration in DataSource project.

    actually i receive this error.

    what shall i do that ServiceReferences.ClientConfig should be available in xap file?

    Cannot find 'ServiceReferences.ClientConfig' in the .xap application package. This file is used to configure client proxies for web services, and allows the application to locate the services it needs. Either include this file in the application package, or modify your code to use a client proxy constructor that specifies the service address and binding explicitly. Please see inner exception for details.

    thanks

    Ivan

  • -_-

    RE: Data Driven Applications with MVVM Part II: Messaging, Unit Testing, and Live Data Sources


    posted by pillesoft on Oct 26, 2010 20:20

    Hi

    found the issue

    the problem was that ServiceReferences.ClientConfig file from DataSources folder should be in "main" project.

    now i go for the 3. part ...

    Ivan

Add Comment

Login to comment:
  *      *       

From this series