The last few WPF/Silverlight projects I’ve worked on we’ve been implementing using the View-ViewModel Pattern, which has worked really well for us – creating testable code with a good separation of concerns. I recently knocked up a very simple Silverlight solution to demonstrate the key aspects of the pattern to a colleague and it seemed to do a pretty good job of conveying the headlines and benefits pretty quickly so thought I’d chuck it up here as well, with a little walk through.
The Application
We’re going to build a simple application that will retrieve a feed (in this case Digg’s main RSS feed) and then display the items in a ListBox. The only other functionality we’ll be offering is the ability to manually refresh the feed. Our business rules state that whilst the feed is updating, the Refresh button must be disabled and the Status displayed. That’s it - exciting huh?
To make this app you will need:
The View
In the View-ViewModel Pattern, your XAML (in our case PageView.xaml) is the View. This is bound to an associated ViewModel (in our case PageViewModel.cs). Our aim is to make the View as dumb as possible (read: “ideally no code in PageView.xaml.cs”) and push all logic back to the ViewModel. Whilst the View is aware of the the ViewModel (via binding), the ViewModel is not aware of the View. This separation makes our ViewModel extremely testable and the View a largely Designer only realm.
Let’s take a look at some code, then we’ll dissect it. Below is the code for PageView.xaml, with the class diagram of PageViewModel along side:
<UserControl x:Class="StandardViewModel.View.PageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:input="clr-namespace:StandardViewModel.SLExtensions.Input"
Width="400"
Height="300"
DataContext="{Binding Path=PageViewModel,
Source={StaticResource ServiceLocator}}">
<Grid x:Name="LayoutRoot"
Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding Path=Items}"
Grid.Row="0"
Margin="5,5,5,5" />
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="Status:"
Grid.Column="0"
Margin="3,3,3,3" />
<TextBlock Text="{Binding Path=Status}"
Grid.Column="1"
Margin="3,3,3,3" />
<Button Content="Refresh"
Grid.Column="2"
Margin="3,3,3,3"
IsEnabled="{Binding Path=IsRefreshEnabled}"
input:CommandService.Command="RefreshCommand" />
</Grid>
</Grid>
</UserControl>
Things to note:
- Besides the standard call to InitializeComponent() – there is no code what so ever in PageView.xaml.cs
- The DataContext for the whole UserControl is set to PageViewModel
- Ultimately the instantiation of PageViewModel is handled for us by Ninject, which I’m not really aiming to cover in this post – download the source code if you want to see how that’s wired in, it’s fairly straightforward.
- Everything is bound to properties in the ViewModel.
- The ViewModel implements INotifyPropertyChanged.
- In the ViewModel IsRefreshEnabled and Status both raise the PropertyChanged event (when their values are changed) and Items is of type ObservableCollection<string> (thus raising the CollectionChanged event when it’s contents are changed).
- The Refresh button has an additional attached property, this is the Silverlight Extensions WPF-like Command implementation.
- This will raise CanExecute and Executed events (ala WPF), that we can subscribe to and handle in the ViewModel.
We’re pretty much done with PageView.xaml and can now concentrate on building our ViewModel to behave correctly, safe in the knowledge data-binding will take care of updating our UI. Since PageViewModel is just a POCO – we can approach this in a TDD manner should we wish, writing Unit Tests against it and then implementing the logic to satisfy the test.
The Model
Although in this example I’m processing an RSS Feed, I don’t want to tie the application to that format, so we’ll implement an IFeedService interface that exposes a GetFeed method and a GetFeedCompletedEvent (raised when the feed has been fetched and parsed). The event returns GetFeedCompletedEventArgs, in which will be a List of FeedItem objects. The actual logic for fetching and parsing an RSS feed will be in RssFeedService, which implements IFeedService. Queue class diagram:
The ViewModel
The only thing our ViewModel now needs to know about is IFeedService, which it will accept as a parameter in it’s constructor. In our application Ninject will bind IFeedService to RssService (letting Ninject take care of instantiation and injection at runtime) and in our unit tests we can write a MockFeedService that implements IFeedService and then use that to instantiate an instance of PageViewModel against which to test.
Our MockFeedService allows us to specify how many items it should generate via a constructor parameter, so we can test that the PageViewModel.Items collection ends up with the correct number of items. Our Unit Test that checks the ViewModel is initialised correctly might look something like this:
[TestMethod]
[Description("Test that the model is correctly initialised at startup")]
public void StartupTest()
{
// Generate 5 Items
MockFeedService mockFeedService = new MockFeedService(5);
PageViewModel pageViewModel = new PageViewModel(mockFeedService);
Assert.AreEqual(Status.Ready, pageViewModel.Status);
Assert.AreEqual(mockFeedService.FeedItems.Count, pageViewModel.Items.Count);
Assert.IsTrue(pageViewModel.IsRefreshEnabled);
}
And the key bits of code from PageViewModel that make that test pass are below (download the source code for the whole thing):
[Inject]
public PageViewModel(IFeedService feedService)
{
this.feedService = feedService;
this.Items = new ObservableCollection<string>();
this.WireUpEvents();
this.CallFeedService();
}
private void WireUpEvents()
{
this.feedService.GetFeedCompleted += this.FeedService_OnGetFeedCompleted;
Commands.RefreshCommand.CanExecute += (sender, e) => e.CanExecute = this.IsRefreshEnabled;
Commands.RefreshCommand.Executed += this.RefreshCommand_OnExecuted;
}
private void CallFeedService()
{
this.Status = Status.Updating;
this.Items.Clear();
this.feedService.GetFeed();
}
private void FeedService_OnGetFeedCompleted(GetFeedCompletedEventArgs args)
{
if (args.Result != null)
{
foreach (FeedItem feedItem in args.Result)
{
this.Items.Add(feedItem.Text);
}
this.Status = Status.Ready;
}
}
The Source Code
You can download the Source code below which contains all the core components and a number of Unit Tests to play with – set the StandardViewModel.Test project as the start-up project and hit F5 to run them.