Architecting SL4 Applications with RIA Services, MEF and MVVM - Part 3


Url: http://wilderminds.blob.core.windows.net/downloads/riaxboxgames.zip

Architecture

UPDATE: There will be a fourth part that discusses the add/remove facility through RIA Services. Look for it soon at a blog near you.

In the first part of this series, I looked at how WCF RIA Services can work in an MVVM architecture. In the second part, I looked at how you could use the Managed Extensibility Framework (MEF) to aid in composing an MVVM application. Now comes the third and last part of the series.  In this part, I will focus on some common problems integrating MVVM and composed user interfaces (that are common to most of these situations, not just using WCF RIA Services). I will show you some refactoring of the MEF design from last article as well as show you how using a framework (I used Laurent Bugnion's MVVM Light framework (v3 Alpha)) can help you smooth out the rough edges of implementing MVVM. 

Refactoring Our MEF Implementation

I had a few conversations with Glenn Block (of MEF fame) about the sample from part 2. His least favorite piece was when I did an Export/Import based on a string like so:

[Import("GameListViewModel")]
public object ViewModel
{
  set
  {
    DataContext = value;
  }
}

His main complaint was that this was not very discoverable. The fact was that I didn't want to use a marker interface (an interface that is just used for identification, and commonly used in IoC scenarios). Marker interfaces, while common, feel like a hack.  The fact is that view models are bound by convention. This means that I didn't want to use some indicator to make it a view model since it should be duck typed if possible.  The more code I wrote to support MEF, the less happy i'd be.

After some discussion, I mentioned that what I really wanted was to have a enum that had values for each type of view model that I could use for the Export/Import. Unfortunately, MEF's imports and exports will support strings and types as keys only.  To avoid having to write my own attributes, I decided to find a middle-ground (that Glenn doesn't hate): const strings. I wrapped them a sealed class like so:

// Not using an Enum as MEF only supports strings and types
public sealed class ViewModelTypes
{
  public const string GameEditorViewModel = "GameEditorViewModel";
  public const string GameListViewModel = "GameListViewModel";
  public const string MenuViewModel = "MenuViewModel";
}

So then I could use them in Exports and Imports like so;

[Import(ViewModelTypes.GameListViewModel)]
public object ViewModel
{
  set
  {
    DataContext = value;
  }
}

The benefit is that they are discoverable (because they are compiled checked) and I don't need to create marker interfaces to use them.  I might add enumerated keys in MEFContrib if I have time. On to other matters...

Commanding in Silverlight 4

In Silverlight 4, ICommand support has been added to several controls including ButtonBase (which is the base class for Button, HyperlinkButton, RepeatButton, RadioButton and CheckBox) as well as the Hyperlink control. This is a way to allow those controls to bind to ICommand implementations so that these controls can execute operations on the ViewModel.  For example, from the GameEditorView.xaml file I am button that is bound to a command like so:

<Button Command="{Binding SaveCommand}"
        CommandParameter="{Binding CurrentGame}"
        Content="Save"
        Width="100"
        VerticalAlignment="Bottom"
        Margin="2 0"
        HorizontalAlignment="Right" />

In this case the SaveCommand property on the ViewModel would need to be an object that supports the ICommand interface.  The ICommand interface supports two methods:

  • bool CanExecute(object): Determines if a command is valid (e.g. can disable a button if it returns false).
  • void Execute(object ): Executes the action.

In each case, the command parameter is sent to the methods if specified. In addition, an event called CanExecuteChanged is supported to let the bindings know that the CanExecute needs to be re-executed.

To create a command normally this means you'd need to implement the interface, but to make it easier the MVVM Light framework includes Josh Smith's RelayCommand<T> which simplifies this a great deal. The basic idea is to create a new object (in this case a save command) and hand it two lambdas that represent the Execute method and the CanExecute method. (For VB.NET people, remember that with the new VB.NET you can do non-functional lambdas so this approach should be very similar).  The RelayCommand takes a generic argument that specifies what is the expected type of the command argument:

// Handle Command
_saveCommand = new RelayCommand<Game>(
  g => _model.SaveGamesAsync(), 
  g => g != null);

The code here says that when executed, it will cause the Save operation to fire and will only be enabled once the game we're handed is not null. You can see a more in-depth explanation of how this works on Laurent Bugnion's website here:

Dealing with Separate Concerns

While a lot has been said about the benefits of separating concerns into a model like MVVM (or MVP or MVC), there are problems when you're dealing with a rich client model like Silverlight or WPF. When you've separated your application into silos, it can be difficult to figure out how to handle cross-silo communication and cooperation. For example, in the example, the GamesListView shows a list of games and when selected needs to notify the GamesListView to show the selected game.  But of course, the views aren't going to communicate with each other since we don't want to couple them.

So how do we make this happen?  We use the MVVM Light's Messenger class. This class uses a simple publish/subscribe model (like Prism's EventAggregator but simpler to use) to allow loosely coupled messaging. To use the messenger, listeners will Register with the messenger and senders will Send with the messenger:

To Register:

Messenger.Default.Register<Game>(this, 
  "GameEditorViewModel",
  g => CurrentGame = g);

To Send:

Messenger.Default.Send<Game>(currentGame, 
  "GameEditorViewModel");

You'll notice that the generic argument is used to specify the payload to the message (in this case, we're passing a game around).  The "GameEditorViewModel" is used as a key so that both sides of the message can register for the same method. The Register method specifies what object is receiving the message (e.g. "this"), the key as well as a lambda that is executed when the message is received.

The messenger pattern here is powerful because except for the shared key, the message is decoupled.  You could traditional events to do this same thing but two things make that difficult.  First, it requires a tight coupling between the ViewModels.  Second (and much more important), it requires you control the lifetime of the two objects.  In the case of the messenger, Register can happen at any time without leaking memory to the sender. If a Send is performed without any registrations, then the send is fairly lightweight.  This pattern is used to represent the mediator between the two objects without holding a strong reference to either (it uses WeakReferences to reduce the possibility of leaking).

Application Level Messages

While communicating between views or viewmodels is common, another common case is to need application level services of one sort or another. For our example, the shell that holds all the views handles error messages and a wait cursor as an application level service.  Error messages are shown in a 'status' message at the bottom of the shell.  In addition, the shell contains a single BusyIndicator to show when any asynchronous communication is being performed.  To communicate between the shell and the different silo's, I also use the Messenger class from the MVVM Light framework.

Because they are at the application level, I did not want to have to have a shared magic string as the key to the message. So I wrapped the messenger into a type-safe wrapper for better discoverability:

public static class AppMessages
{
  enum MessageTypes
  {
    IsBusyMsg,
    StatusChange
  }

  public static class IsBusyMessage 
  {
    public static void Send(bool isBusy)
    {
      Messenger.Default.Send<bool>(isBusy, 
                                   MessageTypes.IsBusyMsg);
    }

    public static void Register(object recipient, 
                                Action<bool> action)
    {
      Messenger.Default.Register<bool>(recipient, 
        MessageTypes.IsBusyMsg, 
        action);
    }
  }
  // More Messages
}

This class simply wraps each message in a static class so they can be called without worrying about the message types or keys. To deal with the IsBusyMessage, instead of calling it in every case, I used the WCF RIA Services' context class to handle whether it was busy doing some communication. The context supports two properties that indicate its working: IsLoading and IsSubmitting. To isolate the message for being busy, I just registered for the context's PropertyChanged event (because the context also implements INotifyPropertyChanged.  In the handler for the event, I simply check to see if either of these properties change and send the message that the IsBusy is enabled or disabled (whether they are true or false):

void _ctx_PropertyChanged(object sender, 
                          PropertyChangedEventArgs e)
{
  switch (e.PropertyName)
  {
    case "IsLoading":
      AppMessages.IsBusyMessage.Send(_ctx.IsLoading);
      break;
    case "IsSubmitting":
      AppMessages.IsBusyMessage.Send(_ctx.IsSubmitting);
      break;
  }
}

For me this is a great solution because even though we're sending messages directly from the Model, the Send does not need to check to see if the Register has been called.  So that in testing cases, it does not break anything.Again, the messaging allows us to stay loosely coupled while providing an elegant way to communicate to any application level services.

Blendability

With all this work done to make sure our application is loosely coupled, we also need to be concerned about how to make the views (or the shell) editable in Blend. The MVVM Light framework takes a specific tactic of using something called a ViewModelLocator to not only be able to match Views with ViewModels but also allows ViewModels to be handed out based on whether it is running in Blend. As I am using MEF to marry the Views and Models, I am not using the ViewModelLocator at all, but in the case of making sure that you can edit the views in Blend, I need to be able to determine whether the application is running in Blend. I do this by using the ViewModelBase's static IsInDesignModeStatic property to test to see if we are in design mode (e.g. in Blend).  Inside my Views I am using this property to only use MEF to satisfy imports when not in design mode:

public GameListView()
{
  InitializeComponent();

  if (!ViewModelBase.IsInDesignModeStatic)
  {
    // Use MEF To load the View Model
    PartInitializer.SatisfyImports(this);
  }
}

This way the ViewModel isn't married at all during design in Blend.  But what if I want data at design time?  That's where I use Blend's Sample Data to bind during design. Sample Data is a feature that was introduced in Blend 3 and allows you to design and edit sample data to be used during design or prototyping.  If you look at the Data tab in Blend, you can create sample data there:

Data Tab 

Once you create a new Sample Data source, you can drag and drop to do the data binding. Remember that since we want the ViewModel to be duck-typed to the data binding this sample data should match the interface you expect in the ViewModel.  For example, here's my sample data for the editor page:

Data Tab 

In this way I can bind the CurrentGame to the individual fields (like the ViewModel does) so that design time I have data to work with:

Data Tab

Because the View doesn't attempt to satisfy its imports while in Design Mode the sample data is use in Blend, but at runtime, the ViewModel will replace the sample data. Corey Schuman has a great walkthrough of sample data here:

Where are We?

While the example is not without its flaws, hopefully it shows off some of the common ways to use WCF RIA Services, MEF and MVVM Light framework to implement a loosely coupled application. You can find the latest version of the sample code (with the MVVM Light assemblies now included) at the following link:

 




Application Name WilderBlog Environment Name Production
Application Ver 1.1.0.0 Runtime Framework .NETCoreApp,Version=v1.1
App Path D:\home\site\wwwroot Runtime Version .NET Core 4.6.25009.03
Operating System Microsoft Windows 6.2.9200 Runtime Arch X86