Share via



January 2011

Volume 26 Number 01

Silverlight Exposed - Using MEF to Expose Interfaces in Your Silverlight MVVM Apps

By Sandrino Di | January 2011

While many developers may think of Silverlight as a Web-centric technology, in practice it has become a great platform for building any type of application. Silverlight has built-in support for concepts such as data binding, value converters, navigation, out-of-browser operation and COM Interop, making it relatively straightforward to create all kinds of applications. And when I say all kinds, I also mean enterprise applications.

Creating a Silverlight application with the Model-View-ViewModel (MVVM) pattern gives you, in addition to the features already in Silverlight, the advantages of greater maintainability, testability and separation of your UI from the logic behind it. And, of course, you don’t have to figure all this out on your own. There’s a wealth of information and tools out there to help you get started. For example, the MVVM Light Toolkit (mvvmlight.codeplex.com) is a lightweight framework for implementing MVVM with Silverlight and Windows Presentation Foundation (WPF), and WCF RIA Services (silverlight.net/getstarted/riaservices) helps you easily access Windows Communication Foundation (WCF) services and databases thanks to code generation.

You can take your Silverlight application a step further with the Managed Extensibility Framework (mef.codeplex.com), also known as MEF. This framework provides the plumbing to create extensible applications using components and composition. 

In the rest of the article I’ll show you how to use MEF to get centralized management of the View and ViewModel creation. Once you have this, you can go much further than just putting a ViewModel in the DataContext of the View. All this will be done by customizing the built-in Silverlight navigation. When the user navigates to a given URL, MEF intercepts this request, looks at the route (a bit like ASP.NET MVC), finds a matching View and ViewModel, notifies the ViewModel of what’s happening and displays the View.

Getting Started with MEF

Because MEF is the engine that will connect all the parts of this example, it’s best to start with it. If you’re not familiar with MEF already, start by reading Glenn Block’s article, “Building Composable Apps in .NET 4 with the Managed Extensibility Framework,” in the February 2010 issue of MSDN Magazine (msdn.microsoft.com/magazine/ee291628).

First you need to correctly configure MEF when the application starts by handling the Startup event of the App class:

private void OnStart(object sender, StartupEventArgs e) {
  // Initialize the container using a deployment catalog.
  var catalog = new DeploymentCatalog();
  var container = CompositionHost.Initialize(catalog);
  // Export the container as singleton.
  container.ComposeExportedValue<CompositionContainer>(container);
  // Make sure the MainView is imported.
  CompositionInitializer.SatisfyImports(this);
}

The deployment catalog makes sure all assemblies are scanned for exports and is then used to create a CompositionContainer. Because the navigation will require this container to do some work later on, it’s important to register the instance of this container as an exported value. This will allow the same container to be imported whenever required.

Another option would be to store the container as a static object, but this would create tight coupling between the classes, which isn’t suggested.

Extending Silverlight Navigation

Silverlight Navigation Application is a Visual Studio template that enables you to quickly create applications that support navigation using a Frame that hosts the content. The great thing about Frames is that they integrate with the Back and Forward buttons of your browser and they support deep linking. Look at the following:

<navigation:Frame x:Name="ContentFrame" 
  Style="{StaticResource ContentFrameStyle}" 
  Source="Customers" 
  NavigationFailed="OnNavigationFailed">
  <i:Interaction.Behaviors>
    <fw:CompositionNavigationBehavior />
  </i:Interaction.Behaviors>
</navigation:Frame>

This is just a regular frame that starts by navigating to Customers. As you can see, this Frame doesn’t contain a UriMapper (where you could link Customers to a XAML file, such as /Views/Customers.aspx). The only thing it contains is my custom behavior, CompositionNavigationBehavior. A behavior (from the System.Windows.Interactivity assembly) allows you to extend existing controls, such as a Frame in this case.

Figure 1shows the behavior. Let’s take a look at what this CompositionNavigationBehavior does. The first thing you can see is that the behavior wants a CompositionContainer and a CompositionNavigationLoader (more on this later) because of the Import attributes. The constructor then forces the Import using the SatisfyImports method on the CompositionInitializer. Note that you should only use this method when you don’t have another choice, as it actually couples your code to MEF.

Figure 1 CompositionNavigationBehavior

public class CompositionNavigationBehavior : Behavior<Frame> {
  private bool processed;
  [Import]
  public CompositionContainer Container { 
    get; set; 
  }
  [Import]
  public CompositionNavigationContentLoader Loader { 
    get; set; 
  }
  public CompositionNavigationBehavior() {
    if (!DesignerProperties.IsInDesignTool)
      CompositionInitializer.SatisfyImports(this);
  }
  protected override void OnAttached() {
    base.OnAttached();
    if (!processed) {
       this.RegisterNavigationService();
       this.SetContentLoader();
       processed = true;
    }
  }
  private void RegisterNavigationService() {
    var frame = AssociatedObject;
    var svc = new NavigationService(frame);
    Container.ComposeExportedValue<INavigationService>(svc);
  }
  private void SetContentLoader() {
    var frame = AssociatedObject;
    frame.ContentLoader = Loader;
    frame.JournalOwnership = JournalOwnership.Automatic;
  }
}

When the Frame is attached, a NavigationService is created and wrapped around the Frame. Using ComposeExportedValue, the instance of this wrapper is registered in the container.

When the container was created, the instance of this container was also registered in itself. As a result, an Import of CompositionContainer will always give you the same object; this is why I used ComposeExportedValue in the Startup event of the App class. Now the CompositionNavigationBehavior asks for a CompositionContainer using the Import attribute and will get it after SatisfyImports runs.

When registering the instance of INavigationService the same thing happens. It’s now possible from anywhere in the application to ask for an INavigationService (that wraps around a Frame). Without having to couple your ViewModels to a frame you get access to the following:

public interface INavigationService {
  void Navigate(string path);
  void Navigate(string path, params object[] args);
}

Now, let’s assume you have a ViewModel showing all of your customers and this ViewModel should be able to open a specific customer. This could be achieved using the following code:

[Import]
public INavigationService NavigationService { 
  get; set; 
}
private void OnOpenCustomer() {
  NavigationService.Navigate(
    "Customer/{0}", SelectedCustomer.Id);
}

But before jumping ahead, let’s discuss the SetContentLoader method in the CompositionNavigationBehavior. It changes the ContentLoader of the Frame. This is a perfect example of the support for extensibility in Silverlight. You can provide your own ContentLoader (that implements the INavigationContentLoader interface) to really provide something to show in the Frame. 

Now that you can see how things start falling into place, the following topic—extending MEF—will become clear.

Back to Extending MEF

The goal here is that you can navigate to a certain path (be it from the ViewModel or your browser address bar) and the CompositionNavigationLoader does the rest. It should parse the URI, find a matching ViewModel and a matching View, and combine them.

Normally you’d write something like this:

[Export(typeof(IMainViewModel))]
public class MainViewModel

In this case it would be interesting to use the Export attribute with some extra configuration, referred to as metadata. Figure 2shows an example of a metadata attribute.

Figure 2 Creating the ViewModelExportAttribute

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ViewModelExportAttribute : 
  ExportAttribute, IViewModelMetadata {
..public Type ViewModelContract { get; set; }
  public string NavigationPath { get; set; }
  public string Key { get; set; }  
  public ViewModelExportAttribute(Type viewModelContract, 
    string navigationPath) : base(typeof(IViewModel)) {
    this.NavigationPath = navigationPath;
    this.ViewModelContract = viewModelContract;
    if (NavigationPath != null && 
      NavigationPath.Contains("/")) {
      // Split the path to get the arguments.
      var split = NavigationPath.Split(new char[] { '/' }, 
        StringSplitOptions.RemoveEmptyEntries);
      // Get the key.
      Key = split[0];
    }
    else {
      // No arguments, use the whole key.
      Key = NavigationPath;
    }
  }
}

This attribute doesn’t do anything special. In addition to the ViewModel interface, it allows you to define a navigation path such as Customer/{Id}. Then it will process this path using Customer as Key and {Id} as one of the arguments. Here’s an example of how this attribute is used:

[ViewModelExport(typeof(ICustomerDetailViewModel), 
  "Customer/{id}")]
public class CustomerDetailViewModel 
  : ICustomerDetailViewModel

Before continuing, there are a few important things to note. First, your attribute should be decorated with the [MetadataAttribute] to work correctly. Second, your attribute should implement an interface with the values you’ll want to expose as metadata. And finally, mind the constructor of the attribute—it passes a type to the base constructor. The class that’s decorated with this attribute will be exposed using this type. In the case of my example, this would be IViewModel.

That’s it for exporting the ViewModels. If you want to import them somewhere, you should be writing something like this:

[ImportMany(typeof(IViewModel))]
public List<Lazy<IViewModel, IViewModelMetadata>> ViewModels { 
  get; 
  set; 
}

This will give you a list that contains all exported ViewModels with their respective metadata, allowing you to enumerate the list and maybe pick out only the ones of interest to you (based on the metadata). In fact, the Lazy object will make sure that only the ones of interest are actually instantiated.

The View will need something similar:

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ViewExportAttribute : 
  ExportAttribute, IViewMetadata {
  public Type ViewModelContract { get; set; }
  public ViewExportAttribute() : base(typeof(IView)) {
  }
}

There’s nothing special in this example, either. This attribute allows you to set the contract of the ViewModel to which the View should be linked.

Here’s an example of AboutView:

[ViewExport(ViewModelContract = typeof(IAboutViewModel))]
public partial class AboutView : Page, IView {
  public AboutView() {
    InitializeComponent();
  }
}

A Custom INavigationContentLoader

Now that the overall architecture has been set up, let’s take a look at controlling what’s loaded when a user navigates. To create a custom content loader, the following interface needs to be implemented:

public interface INavigationContentLoader {
  IAsyncResult BeginLoad(Uri targetUri, Uri currentUri, 
    AsyncCallback userCallback, object asyncState);
  void CancelLoad(IAsyncResult asyncResult);
  bool CanLoad(Uri targetUri, Uri currentUri);
  LoadResult EndLoad(IAsyncResult asyncResult);
}

The most important part of the interface is the BeginLoad method, because this method should return an AsyncResult containing the item that will be displayed in the Frame. Figure 3 shows the implementation of the custom INavigationContentLoader.

Figure 3 Custom INavigationContentLoader

[Export] public class CompositionNavigationContentLoader : 
  INavigationContentLoader { 
  [ImportMany(typeof(IView))] 
  public IEnumerable<ExportFactory<IView, IViewMetadata>> 
    ViewExports { get; set; }
  [ImportMany(typeof(IViewModel))] 
  public IEnumerable<ExportFactory<IViewModel, IViewModelMetadata>> 
    ViewModelExports { get; set; }  
  public bool CanLoad(Uri targetUri, Uri currentUri) { 
    return true; 
  }  
  public void CancelLoad(IAsyncResult asyncResult) { 
    return; 
  }
  public IAsyncResult BeginLoad(Uri targetUri, Uri currentUri, 
    AsyncCallback userCallback, object asyncState) { 
    // Convert to a dummy relative Uri so we can access the host. 
    var relativeUri = new Uri("https://" + targetUri.OriginalString, 
      UriKind.Absolute);  
    // Get the factory for the ViewModel. 
    var viewModelMapping = ViewModelExports.FirstOrDefault(o => 
      o.Metadata.Key.Equals(relativeUri.Host, 
      StringComparison.OrdinalIgnoreCase)); 
    if (viewModelMapping == null) 
      throw new InvalidOperationException( 
        String.Format("Unable to navigate to: {0}. " +
          "Could not locate the ViewModel.", 
          targetUri.OriginalString));  
    // Get the factory for the View. 
    var viewMapping = ViewExports.FirstOrDefault(o => 
      o.Metadata.ViewModelContract == 
      viewModelMapping.Metadata.ViewModelContract); 
    if (viewMapping == null) 
      throw new InvalidOperationException( 
        String.Format("Unable to navigate to: {0}. " +
          "Could not locate the View.", 
          targetUri.OriginalString));  
    // Resolve both the View and the ViewModel. 
    var viewFactory = viewMapping.CreateExport(); 
    var view = viewFactory.Value as Control; 
    var viewModelFactory = viewModelMapping.CreateExport(); 
    var viewModel = viewModelFactory.Value as IViewModel;  
    // Attach ViewModel to View. 
    view.DataContext = viewModel; 
    viewModel.OnLoaded();  
    // Get navigation values. 
    var values = viewModelMapping.Metadata.GetArgumentValues(targetUri); 
    viewModel.OnNavigated(values);  
    if (view is Page) { 
      Page page = view as Page; 
      page.Title = viewModel.GetTitle(); 
    } 
    else if (view is ChildWindow) { 
      ChildWindow window = view as ChildWindow; 
      window.Title = viewModel.GetTitle(); 
    }  
    // Do not navigate if it's a ChildWindow. 
    if (view is ChildWindow) { 
      ProcessChildWindow(view as ChildWindow, viewModel); 
      return null; 
    } 
    else { 
      // Navigate because it's a Control. 
      var result = new CompositionNavigationAsyncResult(asyncState, view); 
      userCallback(result); 
      return result; 
    } 
  }  
  private void ProcessChildWindow(ChildWindow window, 
    IViewModel viewModel) { 
    // Close the ChildWindow if the ViewModel requests it. 
    var closableViewModel = viewModel as IClosableViewModel; 
    if (closableViewModel != null)  { 
      closableViewModel.CloseView += (s, e) => { window.Close(); }; 
    }  
    // Show the window. 
    window.Show(); 
  }  
  public LoadResult EndLoad(IAsyncResult asyncResult) { 
    return new LoadResult((asyncResult as 
      CompositionNavigationAsyncResult).Result); 
  }
}

As you can see, a lot happens in this class—but it’s actually simple. The first thing to notice is the Export attribute. This is required to be able to import this class in the CompositionNavigationBehavior.

The most important parts of this class are the ViewExports and ViewModelExports properties. These enumerations contain all exports for the Views and the ViewModels, including their metadata. Instead of using a Lazy object I’m using an ExportFactory. This is a huge difference! Both classes will only instantiate the object when required, but the difference is that with the Lazy class you can only create a single instance of the object. The ExportFactory (named after the Factory pattern) is a class that allows you to request a new instance of the type of object whenever you feel like it.

Finally, there’s the BeginLoad method. This is where the magic happens. This is the method that will provide the Frame with the content to display after navigating to a given URI.

Creating and Processing Objects

Let’s say you tell the frame to navigate to Customers. This will be what you’ll find in the targetUri argument of the BeginLoad method. Once you have this you can get to work.

The first thing to do is find the correct ViewModel. The ViewModelExports property is an enumeration that contains all the exports with their metadata. Using a lambda expression you can find the correct ViewModel based on its key. Remember the following:

[ViewModelExport(typeof(ICustomersViewModel), "Customers")]
public class CustomersViewModel : 
  ContosoViewModelBase, ICustomersViewModel

Well, imagine you navigate to Customers. Then the following code will find the right ViewModel:

var viewModelMapping = ViewModelExports.FirstOrDefault(o => o.Metadata.Key.Equals("Customers", 
  StringComparison.OrdinalIgnoreCase));

Once the ExportFactory is located, the same thing should happen for the View. However, instead of looking for the navigation key, you look for the ViewModelContract as defined in both the ViewModelExportAttribute and the ViewModelAttribute:

[ViewExport(ViewModelContract = typeof(IAboutViewModel))
public partial class AboutView : Page

Once both ExportFactories are found, the hard part is over. Now the CreateExport method allows you to create a new instance of the View and the ViewModel:

var viewFactory = viewMapping.CreateExport(); 
var view = viewFactory.Value as Control; 
var viewModelFactory = viewModelMapping.CreateExport(); 
var viewModel = viewModelFactory.Value as IViewModel;

After both the View and the ViewModel have been created, the ViewModel is stored in the DataContext of the View, starting the required data bindings. And the OnLoaded method of the ViewModel is called to notify the ViewModel that all the heavy lifting has been done, and also that all Imports—if there are any—have been imported.

You shouldn’t underestimate this last step when you’re using the Import and ImportMany attributes. In many cases you’ll want to do something when creating a ViewModel, but only when everything has been loaded correctly. If you’re using an ImportingConstructor you definitely know when all Imports were imported (that would be when the constructor is called). But when working with the Import/ImportMany attributes, you should start writing code in all your properties to set flags in order to know when all properties have been imported.

In this case the OnLoaded method solves this issue for you.

Passing Arguments to the ViewModel

Take a look at the IViewModel interface, and pay attention to the OnNavigated method:

public interface IViewModel {
  void OnLoaded();
  void OnNavigated(NavigationArguments args);
  string GetTitle();
}

When you navigate to Customers/1, for example, this path is parsed and the arguments are combined in the NavigationArguments class (this is just a Dictionary with extra methods like GetInt, GetString and so on). Because it’s mandatory that each ViewModel implements the IViewModel interface, it’s possible to call the OnNavigated method after resolving the ViewModel:

// Get navigation values. 
var values = viewModelMapping.Metadata.GetArgumentValues(targetUri); viewModel.OnNavigated(values);

When the CustomersViewModel wants to open a CustomerDetailViewModel, the following happens:

NavigationService.Navigate("Customer/{0}", SelectedCustomer.Id);

These arguments then arrive in the CustomerDetailViewModel and can be used to pass to the DataService, for example:

public override void OnNavigated(NavigationArguments args) {
  var id = args.GetInt("Id");
  if (id.HasValue) {
    Customer = DataService.GetCustomerById(id.Value);
  }
}

To find the arguments, I wrote a class containing two extension methods that do some work based on the information in the ViewModel metadata (see Figure 4). This proves again that the metadata concept in MEF is really useful.

image: Extension Methods for Navigation Arguments

Figure 4 Extension Methods for Navigation Arguments

The Final Chores

If the View is a Page or a ChildWindow, the title of this control will also be extracted from the IViewModel object. This allows you to dynamically set the titles of your Pages and ChildWindows based on the current customer, as shown in Figure 5.

image: Setting a Custom Window Title

Figure 5 Setting a Custom Window Title

After all these great little things, there’s one last step. If the View is a ChildWindow the window should be displayed. But if the ViewModel implements IClosableViewModel, the CloseView event of this ViewModel should be linked to the Close method on the ChildWindow.

The IClosableViewModel interface is simple:

public interface IClosableViewModel : IViewModel {
  event EventHandler CloseView;
}

Processing the ChildWindow is also trivial. When the ViewModel raises the CloseView event, the Close method of the ChildWindow gets called. This allows you to indirectly connect the ViewModel to the View:

// Close the ChildWindow if the ViewModel requests it.
var closableViewModel = viewModel as IClosableViewModel;
if (closableViewModel != null) {
  closableViewModel.CloseView += (s, e) => { 
    window.Close(); 
  };
}
// Show the window.
window.Show();

If the View isn’t a ChildWindow, then it should simply be made available in the IAsyncResult. This will show the View in the Frame.

There. Now you’ve seen the whole process of how the View and ViewModel are constructed.

Using the Example Code

The code download for this article contains an MVVM application using this type of custom navigation with MEF. The solution contains the following examples:

  • Navigating to a regular UserControl
  • Navigating to a regular UserControl by passing arguments (.../#Employee/DiMattia)
  • Navigating to a ChildWindow by passing arguments (.../#Customer/1)
  • Imports of the INavigationService, the IDataService, ...
  • Examples of ViewExport and ViewModelExport configuration

This article should have provided a good idea of how the sample works. For a deeper understanding, play with the code and customize it for your own applications. You’ll see how powerful and flexible MEF can be.     


Sandrino Di Mattia is a software engineer at RealDolmen and has a passion for everything that is Microsoft. He also participates in user groups and writes articles on his blog at blog.sandrinodimattia.net.

Thanks to the following technical experts for reviewing this article: Glenn Block and Daniel Plaisted