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:
- I need to persist the changes in the data store.
- 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.
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.)
Now I can add the Book class as a link.
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.
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