Tuesday, August 10, 2010

Sharing Silverlight Commands with the Managed Extensibility Framework

View models can expose commands that are bound to controls. Commands encapsulate an action and a permission, and the advantage to binding is that controls that support commands will automatically disable when the command cannot execute. Commands can also be easily added to view models and tested.

It is common to find commands that are used universally. In Windows Presentation Foundation (WPF), this is managed by a set of global commands that can be accessed via static classes. Silverlight does not provide the equivalent, but it is easy enough to compose and share commands using MEF.

There were specifically two scenarios I needed to tackle in a recent project.

The First Scenario: Command Sharing

It is quite common to have a command that can be executed from multiple places. An "expand" command, for example, represents a generic behavior that is applied to specific controls. If the behavior can be extrapolated from the control itself, why duplicate it across multiple controls? It is far more efficient to define the behavior in one place and then share the behavior where it is needed.

The Second Scenario: Late Command Binding

Sometimes commands may provide a layer of de-coupling. Specifically, I wanted to provide a command to switch to a different type of view. Because my view models are shared by different views, I didn't want to create a tight coupling by referencing the views directly. In fact, the views were being dynamically loaded in a separate XAP file, so there was no reference I could create in the view model. To solve this I used MEF to late-bind the command when the view becomes available.

The Setup

The setup is quite simple. Because I want to import the command directly, I'm not going to use metadata. Instead, I'll create a common static class that is shared across my project that exposes some constants to define the commands. I'll use the constants as the contract name for importing and exporting. It looks like this:

public static class Commands 
{
   public const string EXPORT_COMMAND = "ExportCommand";
}

Now I can request the command in my view model, by importing it directly:

public class MyViewModel
{
   [Import(Commands.EXPORT_COMMAND, AllowDefault=true)]
   public DelegateCommand<object> ImportedCommand { get; set; }
}

The "allow default" is important. If I don't have the command available, MEF will throw an exception when trying to set the import. With allow default, the property simply remains as null until the import can be satisfied. Once my dynamic module loads with the corresponding export, it will be wired in for me. Note that I can import the same command in multiple places to satisfy the need to share it.

Now I can export the command. I might create a class specifically for this or place it in a view model in the imported XAP file. Regardless, an exported command looks something like this:

public class Exports
{
   [Import]
   public INavigation Navigation { get; set; }

   [Export(Commands.EXPORT_COMMAND)]
   public DelegateCommand<object> ExportedCommand 
   {
      get 
      {
         return new DelegateCommand<object>(
            obj => Navigation.NavigateTo(typeof(SomeView)));
      }
   }
}

This way I'm able to expose a command for navigation without knowing what the view or navigation looks like ... instead, I let the XAP that does know handle it for me.
Jeremy Likness