Architecting Silverlight 4 with RIA Services, MEF and MVVM - Part 1

Architecture

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://wildermuth.com/downloads/riaxboxgames.zip

Comments:

Gravatar

The one part I would disagree with is your use of ObservableCollection. I suggest exposing IEnumerable<Game> from the model instead of ObservableCollection<Game>. This would allow you to directly return EntitySets or EntityCollections from the DomainContext through your Model. For situations where I want to expose a subset of the EntitySet I have been using the PagedCollectionView.

Gravatar

Colin,

Good point about IEnumerable<T>, I like the solution you suggested.

Gravatar

Shawn, you may not be a Wall Street Fat Cat, but you're still doing God's work(lol).

This is great stuff, and I look forward to the following articles in this series. BTW, I've had similar concerns, and I am a heavy user of both MEF and MVVM Light(had to get my bias out there).

Gravatar

Thanks Shawn. Looking forward to the MEF integration post. Also, this post led me to discovering the MVVM Light Toolkit - wow, very cool.

Gravatar

Actually I agree with Colin. By using OC u immediatly loose change tracking which you'll probably need. Ienumerable and pagedcollectionview seems to be okay( too bad dataform has issues with add support for those two.) btw i have a question. You have a dataform with a combobox inside among others. How do you fill up ur combobox dynamically when u have the vm demonstrated anove?

Gravatar

Zoltan,

The OC doesn't make you lose change tracking (only change tracking for add/remove which i will likely need). I don't have any dataforms. Its all just XAML. I am not yet filling the combo box from RIA, i'll probably add that by the last blog in the series. It would be exposed off the menuviewmodel and just be a call into the model.

Gravatar

Shawn,

Your point on Zoltan on the lack of add/remove change tracking reminded me of another point. If you are going to expose an ObservableCollection then you should wrap it in a ReadOnlyObservableCollection to make sure that the lack of add/remove support is enforced.

Gravatar

I notice that you are exposing your DAL types directly to the ViewModel, and also bind it to the View. In this case you will have several dependencies to the DAL model in different tiers and layers. A simple changes on the server-side model can lead to changes in several places. In this case I should use DTO instead of exposing DAL types, the DTO will be designed in a way that only data the View is needed it passed to the View. The DomainService will use the Transform pattern to transform the DAL or domain entities to DTOs.

Gravatar

Fredrik,

I considered this for this example. Generally only the shape of the types are changing and since data binding is loose, I think the dependency is ok. Though I could see using DTO's in several places (e.g. in the server-side DomainService). The fact that the EF model is a 1-to-1 model should be ignored as all the RIA is doing is exposing the EF model. I think that separation from the database to entities is where i'd do it. I have never been a big fan of DTO's but I do see benefit in the isolation in some cases, just not the majority case. But thanks for the input.

Gravatar

Everything depends on how large your app is, how the user will use the data etc. When you create the EF model, do you for example have the View in mind, and also what data the View is needed? If so, in a small app there is often no problems of exposing DAL types. Most RIA will also need business logic on the server-side, in this case the Domain Service is just a service-layer and should not expose business domain entities or DAL types, they are often not designed for presentation in mind. In some cases it can also be good to transform the DTO to another "presentation model" in the presentation layer and expose the new model from the ViewModel. By doing so, optimization or some specific changes to server-side model or DTO can be done without affecting the View, and will decrease number of changes, but can also lead to more changes. So there is not Silver bullet. I talked to a customer today after my RIA talk, he exposes domain entities. They have a major problem right now, they pass to much data over the wire because the models aren't designed with presentation in mind. They now notice the need of using DTO for data projection and also minimizing the data passed to the client side.

Gravatar

Fredrik,

This model of what you expose over the wire is specifically why I very commonly use WDS instead. Projections in v2 allows me to create DTO's on the fly and not confuse the entities with the view requirements...but that's a different discussion.

Gravatar

You are too kind, Shawn. The "purist" argument for "screen DTOs" and against exposing entities to view binding is YAGNI and misguided. It actually makes code harder to read, harder to write, more brittle, and more expensive.

It obliges you "on principle" to write anemic DTO classes and mapping code in anticipation of trouble that most likely never arrives ... as if the DTOs and mapping were cost and maintenance free.

It invites premature optimization (e.g., unsubstantiated concern about retrieving more entity data than is actually needed ) without considering the accompanying complexities (e.g., changes to entities not propagated to their projections) that bloat the application with code and mistakes.

As Shawn observes, the view has no idea what it is binding to. If the entity has more properties than are displayed, so what? When you produce a measurable degradation in response, I'll listen.

Additive changes to the entity are harmless to the view. Deleting a property is harmless too ... unless the property is displayed in which case you have the multiple maintenance burdens of adjusting the DTO, mapping, and entity. The notion that the view is impervious to a substantive change to the entity or the backing table(s) defies experience and common sense.

Finally, if the need arises to replace binding to the entity with binding to a ViewModel that wraps/reshapes the entity ... if you discover that the entity is unsuitable for presentation (which occurs < 5% of the time), you can introduce the "DTO" with no impact on the view or surrounding code. In other words, it is an easy, low impact refactoring that can and should wait.

Time to call B.S. on the mania for mapping to "screen DTOs". You do that when you have a proven need ... and not before.

Gravatar

As usual Ward, you're my hero ;)

Gravatar

I couldn't agree more with Ward Bell's comment. Why introduce all of that code before you have to. Keep it simple as long as you can and the refactored when you have to, if you ever have to

Gravatar

I was reading the comments and could not agree more with Ward. The presentation model should only be introduced in specific cases, especially when using RIA Services.

If you're going to do this by hand, you might as well just map your presentation Model to your DAL with nHibernate and create your own WCF comm. layer and forget all about RIA.

Be careful with the presentation model and only use it in specific cases or this is really not going to gain you anything.

Gravatar

It's based on the app your are building.. there is no Silver bullet, no one have ever stated that DTO should always be used. But they will often fit well in enterprise apps. If you don't agree, you don't have to. This is based on my 13 years of writing distributed enterprise apps, and maybe I just didn't had the luck to be in the perfect enterprise project ;)

Gravatar

Why did you choose MVVM Light over the many other available toolkits? Do you use MVVM Light in your commercial engagements?

Gravatar

I third what Ward says: don't try too hard to be a purist until - at least until you really need to.

On that note, Shawn, if it's so easy to generate an interface from your viewmodel "just in case", then why are you doing it before you "need" it?

It seems easier to me to bind to a concrete viewmodel, and anyway, XAML bindings are usually not strongly typed anyway (ie, you can substitute any object of the same shape, even without a common base class)

If I was unit testing a view I would probably still prefer to mock out my model than my viewmodel, otherwise all you're testing is Microsoft's binding code.

Gravatar

I third what Ward says: don't try too hard to be a purist until - at least until you really need to.

On that note, Shawn, if it's so easy to generate an interface from your viewmodel "just in case", then why are you doing it before you "need" it?

It seems easier to me to bind to a concrete viewmodel, and anyway, XAML bindings are usually not strongly typed anyway (ie, you can substitute any object of the same shape, even without a common base class)

If I was unit testing a view I would probably still prefer to mock out my model than my viewmodel, otherwise all you're testing is Microsoft's binding code.

Gravatar

@Shawn:
Great post! I'm excited to see what more is in store!

@Ward:
I don't know what sorts of simple demo apps you are building, but using connected entities straight from LINQ to SQL has bitten us hard so many times on the app I'm working on now, that I would happily take the relatively small amount of complexity of using "anemic" DTO's. (Btw, DTO's are by definition "anemic", so I'm not certain what you were trying to say there.) I suppose this is yet another case of YMMV, but immediately discounting the need for DTO's is ridiculous.

Gravatar

Ryan,

I generally think that using DTO's in every case isn't necessary. Anyway, its in Silverlight so you're getting data copies anyway (sure the interface is tied to the original objects). But RIA will let you expose your DTO's if you prefer...not a big deal in any case.

Gravatar

About DTO, some developers are used to simple dataform apps and don't have experience in building large enterprise distributes apps, or are still stuck with the old dataform binding thinking. Every enterprise pattern has its purpose to solve problems, so they aren't there to just make people angry ;) Why does WCF use DataContracts and why did DCOM fail, just a thought. It has to do with the distribution. There is a reason why well known architects say "Don't distribute objects", "Data Transfer Object are concepts to make remote architectures work". Read the following, it will be of great interest for everyone I think:
http://martinfowler.com/bliki/FirstLaw.html

http://www.ddj.com/architect/184414966.

Note: The use of DTO is based on the apps we are bulding, and in some cases they may be not needed at all.


 



 
Save Cancel