Thanks for visiting my blog!
Url: http://wilderminds.blob.core.windows.net/downloads/riaxboxgames.zip
Recently I blogged about Brad Abrams’ PDC RIA Services Talk and complained about the data source functionality. While the drag-n-drop ability in RIA Services is interesting, I believe that it may be a bad approach for all but the smallest of projects (or one-off projects). In that comments of that article, I promised to show you how I would architect a Silverlight solution with RIA Services.
The outcome of that work is a sample that I will cover in a series of blog posts (starting with this one) to explain not only how i’d use RIA Services in Silverlight 4, but also how to solve some of the basic difficulties with those types of architectures. I will be covering how I integrated the Managed Extensibility Framework (MEF) and Laurent Bugnion’s MVVM Light Framework to stitch together a loosely coupled Silverlight applciation. But let’s start with RIA Services.
At the core, RIA Services is a way of manipulating data across an Internet connection. While it provides other services like shared code and communication of validation attributes, I am going to focus on its use as a data provider for Silverlight 4.
Like I’ve discussed in articles, the Silverlight Tour and in blog posts, I have been recommending the Model-View-ViewModel pattern for separating concerns in Silverlight. This is especially true for data-driven or line-of-business applications. If you are building widgets or video players, this pattern may be more work than necessary. But for larger applications, this pattern allows us to separate the layers and test each of the layers in isolation. This provides a bedrock of testable code and regression testing that makes our software predictably written. So let’s look at some code.
As I mentioned in my MVVM Article in MSDN Magazine, I believe the Model is best expressed as a set of operations that retrieve data. It may be temping to simply use the data context itself as the model. The reason that using the data context means we would need to mock the entire data context surface area to use it for testing. Additionally, creating a custom model allows us to isolate what transport layer we’re using so we can change it or even have several data providers specifying data for our model. For example, in the example application, I am isolating the RIA Services’ data context inside the model like so:
public class GamesModel : IGamesModel
{
public void GetGamesByGenreAsync(string genre) // ...
public void SaveGamesAsync() // ...
XBoxDomainContext _ctx;
public event EventHandler<GameResultsArgs> GetGamesComplete;
public event EventHandler<ResultsArgs> SaveGamesComplete;
}
Because the model is for Silverlight 4, the methods that retreive data are asynchronous (and the event handlers make retrieving the results (or errors) simple. Because the model is implementing an interface (IGamesModel) we can mock the model as necessary to test other parts of the system (primarily ViewModels).
Notice in the model, I have both retrieval (GetGamesByGenreAsync) and updating (SaveGamesAsync). One of my basic ideas here is that the model is not only a conduit but also where the state for changed objects are held. In that way, by the model holding onto the data context, it can keep tracked entities around so when save is called, we don’t need to tell it what entities…it already knows what has been changed (though we may need an AddGame and DeleteGame method on the model if we wanted to support new and deleted objects).
But why does this work? It works because data binding in Silverlight 4 is powerful. When we pass the objects through to the ViewModel (and ultimately to the View), any changes that happen are going to notify anyone who cares because of the INotifyPropertyChanged interface. One of the listening parties to the INotifyPropertyChanged interface is the data context itself. That’s how it knows when an object has changed. So when we create a view model (I am using Laurent Bugnion’s MVVM Light’s ViewModelBase as the base class for my view models), we can expose a bindable surface for the view to use, like so:
public class GameListViewModel : MyViewModelBase, IGameListViewModel
{
IGamesModel _model;
public GameListViewModel(IGamesModel theModel)
{
_model = theModel;
_model.GetGamesComplete +=
new EventHandler<GameResultsArgs>(_model_GetGamesComplete);
}
private ObservableCollection<Game> _games;
public ObservableCollection<Game> Games
{
get { return _games; }
private set
{
if (value != _games)
{
_games = value;
RaisePropertyChanged("Games");
}
}
}
// ...
}
You should see here in the viewmodel that I am accepting the interface for the model (so the viewmodel can be tested in isolation). Also, note that the ViewModels also use an interface so they can be mocked but testing Views in isolation is not an easy task so far. I do this by habit for the days when it will be possible. One trick to make this easier, is to just write the View or ViewModel class first then extract the interface (using the VS refactoring tools or another 3rd party tool like refactor.
Because the view model is responsible for creating a bindable interface for the view, we can expose the games as an ObservableCollection so that changes are correctly monitored by the bindings. In this case take the Games I exposed in the viewmodel and directly bind it to a ListBox so the user can specify specific games as shown below:
<ListBox ItemsSource="{Binding Games}"
DisplayMemberPath="Name"
x:Name="theList"
VerticalAlignment="Stretch" />
For individual games, we expose them directly from the selected index item (which I’ll explain in Part 3 of this series) by binding to the CurrentGame element of the editor’s own viewmodel:
<StackPanel DataContext="{Binding CurrentGame}">
<TextBlock>Name</TextBlock>
<TextBox Text="{Binding Name, Mode=TwoWay,
TargetNullValue='(None)',
ValidatesOnExceptions=True,
NotifyOnValidationError=True}" />
<TextBlock>Description</TextBlock>
<TextBox Text="{Binding Description, Mode=TwoWay,
TargetNullValue='(None)',
ValidatesOnExceptions=True,
NotifyOnValidationError=True}"
AcceptsReturn="True"
TextWrapping="Wrap"
VerticalScrollBarVisibility="Auto"
Height="100" />
<TextBlock>Price</TextBlock>
<TextBox Text="{Binding Price, Mode=TwoWay,
TargetNullValue='(None)',
StringFormat=c,
ValidatesOnExceptions=True,
NotifyOnValidationError=True}" />
<TextBlock>Release Date</TextBlock>
<my:DatePicker SelectedDate="{Binding ReleaseDate, Mode=TwoWay,
ValidatesOnExceptions=True,
NotifyOnValidationError=True}" />
<dat:ValidationSummary />
</StackPanel>
Since these are going to be the actual games from RIA Services, we can use the Validation Summary and the binding properties for exposing our validation attributes. This might seem like a tightly bound contract between the exposed elements in RIA Services and the data bindings but I contend that its not. These bindings simply say that it should deal with any validation necessary. The fact that the RIA Services’ implementations do validation using validation attributes is just a side affect.
At this point you should see some simple patterns for creating multiple-views and viewmodels while wrapping RIA Services without ever using the DataSource control. In the next part of this series I will show you how we’ll use MEF to link the views, viewmodels and the model while allowing us to test and compose our application. You can see the demonstration code here:
http://wilderminds.blob.core.windows.net/downloads/riaxboxgames.zip