MVVM Instantiation Approaches
As a pattern, there's a lot of flexibility and choice available when implementing the Model-View-ViewModel pattern. However, no matter how you go about it, there are a few things you'll have to do:
- Instantiate the view
- Instantiate the view model
- Connect the view to the view model, so that you can bind to it
There are many ways to accomplish this, and different resources on the pattern show different ways. This page discusses some of those approaches, and attempts to give them a name.
I would love to know if you have seen alternative patterns in the wild, and any pros/cons you have experienced using these approaches.
Option 1: Internal creation:
This is the approach I generally start with when introducing the pattern. In this case, the VM is just a private "implementation detail" of the view, while still being testable.
public CalculatorView()
{
InitializeComponent();
DataContext = new CalculatorViewModel();
}
Option 2: ViewModel as a dependency:
This is what I usually evolve the first example into, so that we can start to talk about DI and the use of containers.
public CalculatorView(CalculatorViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
Then when you navigate to the view (note: you'd probably want to use an IOC container to instantiate these):
var viewModel = new CalculatorViewModel();
var view = new CalculatorView(viewModel);
Option 3: External creation and assignment:
In this approach, the View doesn't even know how its DataContext
will be set - our navigation code "peers in" to the view:
var view = new CalculatorView();
var viewModel = new CalculatorViewModel();
view.DataContext = viewModel;
Option 4: ViewModel as a XAML property value:
Instead of code, some people like to use XAML to create and assign the view model:
<UserControl ...>
<UserControl.DataContext>
<local:CalculatorViewModel />
</UserControl.DataContext>
Some choose to just use this at design time, and replace it at runtime, using one of the options above to override the DataContext
.
Option 5: ViewModel as a XAML resource:
Some eschew DataContext all together, and go with a resource. This approach worked very well with earlier versions of Expression Blend.
<UserControl ...>
<UserControl.Resources>
<local:CalculatorViewModel x:Key="Model" />
</UserControl.Resources>
<TextBox Text="{Binding Source={DynamicResource Model}, Path=...}" />
This is also sometimes replaced at runtime using options 1-3, for example:
public CalculatorView(CalculatorViewModel viewModel)
{
InitializeComponent();
Resources["Model"] = viewModel;
}
Option 6: A XAML View Model Locator:
Rather than constructing the view model, some use a locator to resolve the ViewModel, while still allowing it to be used as a resource (and thus get a nice design experience). Others use ObjectDataProvider for a similar purpose. This approach has been popularized by MVVM Light:
<UserControl ...>
<UserControl.Resources>
<ViewModelLocator x:Key="ViewModelLocator"/>
</UserControl.Resources>
<TextBox
Text="{Binding Source={DynamicResource ViewModelLocator}, Path=CalculatorViewModel...}"
/>
Option 7: DataTemplate as views
Some lunatics don't use a real view at all, but instead just use a DataTemplate
. These people should not be allowed near sharp objects :-)
<DataTemplate DataType="{x:Type local:CalculatorViewModel}">
<... />
</DataTemplate>
<!-- Because of the DataType, this will automatically select the template above -->
<ContentPresenter Content="{Binding Path=Model}" />
Option 8: Data Template and View
Similar to 7, this approach uses a data template to select the appropriate view for a given view model, but the view still has its own class. Thanks to Marek, Ian and Daniel Spruce (see comments below) for pointing out this alternative:
<DataTemplate DataType="{x:Type ViewModels:CalculatorViewModel}">
<Views:CalculatorView />
</DataTemplate>
Personally, I tend to use Option 2 (ViewModel as dependency) if I'm not using a framework, otherwise Option 3 (External creation and assignment). I either forgo designer support or rely on tools such as the newer Blend sample data support, so the other approaches aren't too useful to me. Magellan uses option 3 by default.
Which approach do you use? Do you do something different? Care to share a sample?
Update: I also posted this to the WPF Disciples list - you can see what some of the other disciples had to say here.