Some time back, I blogged about different options to hook up a view to its view model when following the ViewModel (aka MVVM) pattern. There are multiple approaches in use out there. I raised the possibility of a convention-based approach in addition to existing ones like the ViewModelLocator pattern.
Motivation: I find the simplest way to explain ViewModel is to present it as a better code-behind, especially to the mainstream developer who is intimately familiar with the latter (at least in the .net world). With the code-behind approach the UI, its state and operations are all mixed up. The ViewModel pattern is of course designed to help separate application logic from the view, but now results in two discrete halves that need to be reconnected. Result: additional concept count. With a convention-based approach, I believe the simplicity of the code-behind model can be achieved while preserving the ability to decouple.
Like most other convention-based systems, it addresses the common and mainline scenarios well. There will always be some more advanced or less common scenarios that will fall out of the convention, for which a configuration-based approach needs to exist alongside.
With that context, I want to share what Silverlight.FX (@silverlightfx) now offers around hooking up a view to its view model by following a convention over configuration approach, to provide simplicity while retaining flexibility.
Convention
Silverlight.FX provides a convention-based approach, and after having using it for a while, it has worked for close to 100% of the scenarios I have personally encountered.
Quite simply the convention is for the view to look for a class in the same namespace/assembly with a type name formed by taking the view's type name and appending either a "Model" or "ViewModel" suffix.
I usually structure my view and associated view model as shown in the screenshot of the solution explorer and the convention matches that mental organization model. The snippets of code and markup below represent the typical content in each of those files:
MainView.xaml (XAML markup)
<fxui:View
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:fxui="clr-namespace:SilverlightFX.UserInterface;assembly=SilverlightFX"
x:Class="MyApp.MainView">
...
</fxui:View>
MainView.xaml.cs (code-behind)
namespace MyApp {
public class MainView : View {
public MainView() {
InitializeComponent();
}
}
}
MainView.Model.cs (view model)
namespace MyApp {
public class MainViewModel : Model {
/* properties, methods and events */
}
}
So you might be wondering about <fxui:View>. The convention is implemented by the View base class. View is simply a UserControl with the smarts on locating a view model class, instantiating it (through an IoC container if one is present), and setting it up as the Model and DataContext for itself.
Note, that there are a couple of interesting derived classes deriving from View: Page which adds navigation semantics, and Form which adds dialog semantics that derive from View. All of these support the convention-based ViewModel association.
There are some cases where the above convention doesn't work. The scenario I've seen is where you have a view model that is shared by multiple views. This leads to needing a configuration-based approach.
Configuration via Attribute - Specifying View Model Type
The simplest way to specify the view model type using Silverlight.FX is to annotate the view with the [ViewModel] metadata attribute. The View base class shown above looks for this attribute before it tries to look for a type matching the above convention.
For example if I have a ViewModel such as CustomerViewModel that I want to share across a couple of forms, such as CustomerEditForm and CustomerInsertForm, I can do the following, without having to do anything in the XAML markup in those views:
// The views - forms to edit and insert customers
// (both associated with the same view model type)
[ViewModel(typeof(CustomerViewModel)]
public class CustomerEditForm : Form {
}
[ViewModel(typeof(CustomerViewModel)]
public class CustomerInsertForm : Form {
}
// View model that is shared by both forms above
public class CustomerViewModel : Model {
}
So far what we've seen is relying on the View base class and having it instantiate the view model type. There might be cases where you want to use a vanilla UserControl, or there might be cases where you want to take over the instantiation of the view model type yourself.
Configuration via Attached Property - Using a ViewModelLocator
Silverlight.FX provides a View.Model attached property. Using that allows you to associate the view model explicitly (to any UserControl, or View), as well as take on the responsibility of creating and initializing the view model.
For example, here is a UserControl with an explicitly set ViewModel:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:fxui="clr-namespace:SilverlightFX.UserInterface;assembly=SilverlightFX"
fxui:View.Model="{Binding CustomerListModel, Source={StaticResource ViewModelLocator}}">
</UserControl>
Here's how I would define my ViewModelLocator class within my application:
namespace MyApp {
public sealed class ViewModelLocator {
public CustomerListModel CustomerListModel {
get {
/* create and return an instance of CustomerListModel */
}
}
}
}
And finally, I would declare an instance of the ViewModelLocator in application resources, so that all views in the application can bind to it:
<Application
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app="clr-namespace:MyApp">
<Application.Resources>
<app:ViewModelLocator x:Key="ViewModelLocator" />
</Application.Resources>
</Application>
Notice that I bind View.Model, rather than DataContext as you might have seen in other ViewModelLocator demos. This has the effect of setting up View.Model, and setting DataContext as a side-effect. This allows the ability to lookup the model associated with the view anywhere within the view, even if the DataContext changes as you go deeper into the visual tree. For example, an item in an ItemsControl has a different DataContext, but shares the same view model, and is able to access it.
Design-time Experience
At design-time, designers like Blend and Cider need information about the type of the value assigned to the DataContext of a view, so various binding pickers and dialogs can understand the object model to display properties to bind to.
This can be accomplished using a design-time DataContext value. For example:
<fxui:View
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:fxui="clr-namespace:SilverlightFX.UserInterface;assembly=SilverlightFX"
xmlns:app="clr-namespace:MyApp"
x:Class="MyApp.MainView"
d:DataContext="{d:DesignInstance Type=app:MainViewModel}">
...
</fxui:View>
Of course, I'd like the tools to also become aware of and support the convention, so that the design-time DataContext can be inferred without explicit assignment. I am working with Blend/Cider on this... it might be possible to implement support for this today using Cider's design surface extensibility - something to look at.
Summary
I personally find the convention approach quite compelling. I've in fact changed all the Silverlight.FX samples to use it. At the same time, I recognize a configuration approach is essential when you need it, and it can co-exist and step in when convention doesn't scale to the requirements at hand. However by default, the convention simplifies and cleans up things.
One of the reasons why I'm sharing Silverlight.FX, besides it being a fully usable application framework for your own applications, is to hear your thoughts on ideas such as this. So please do share your feedback, as I am working with the runtime and tools folks on additional view model support going forward.
This was implemented in Silverlight.FX since late last year, and now I've created and published an updated public build (version 3.4) of the framework along with sources (if you're interested in the behind-the-scenes details), and sample applications (to see this work in practice). Head over to the Silverlight.FX project page to download the latest release. This latest version contains a number of other controls and a few renames as well (as I start preparing for a Silverlight 4 version of the framework). All of these are features and changes are documented within the History.txt file when you download the zip.
On a side note, you can follow me, @nikhilk for quick updates and/or Silverlight.FX, @silverlightfx for updates/announcements via Twitter in addition to blog posts.