Introduction
Recently I entered the wonderful world of MVVM. There is a lot to read on this subject (which I did) and there are several very good frameworks available. So I 'collected' handy bits and pieces from these frameworks in order to create my first MVVM application.
During this process, I found out that the ViewModel locator was debated a lot and that all the various solutions had their pros and cons (see: Nikhil Kothari Options for Hooking a View to its Model). Since the introduction of MEF in .NET Framework 4, a new possibility came into reach which was described by (johnpapa: Simple ViewModel Locator for MVVM: The Patients Have Left the Asylum).
Basically what it comes down to is how to you 'wire' your ViewModel to your view without specifying it in multiple places and with the preservation of designtime binding in Blend and VS2010.
Since code says more than an article, I included a simple project which demonstrates how it works.
DynamicObject
The backbone of my solution is the new DynamicObject
which was introduced in .NET Framework 4. This object can be extended runtime, if you want more information on how these things work, you can find it here: A Multi-level C# 4.0 Dynamic Object. When a property is used from a DynamicObject
, the TryGetMember
is called with the name of the property which is requested. When a DataContext
is specified, the property name can be specified like this:
DataContext="{Binding Demo1, Source={StaticResource Locator}}"
Since my Locator is a DynamicObject
, the TryGetMember
is called with the parameter name "Demo1
". Initially, I then created the corresponding ViewModel
based on this name like this:
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
string name = binder.Name;
if (!dictionary.TryGetValue(name, out result))
{
switch (name)
{
case "Demo1":
dictionary[name] = (result = new Demo1ViewModel());
return true;
}
return false;
}
return true;
}
But when I read the article by JohnPapa, I decided to add his MEF implementation as well! Also a lot of articles on this subject can be found here: .NET 4.0 MEF FAQ. In order to find the ViewModel
, an ExportViewModel
attribute is introduced:
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class ExportViewModel : ExportAttribute
{
public string Name { get; private set; }
public ExportViewModel(string name, bool isStatic)
: base("ViewModel")
{
Name = name;
}
}
Which can be used on a ViewModel
like this:
[ExportViewModel("Demo1", false)]
class Demo1ViewModel : ViewModel
{
public Demo1ViewModel()
{
EnDisable = new RelayCommand(() =>
{
isEnabled = !isEnabled;
Action.RaiseCanExecuteChanged();
});
Action = new RelayCommand(() =>
MessageBox.Show("demo1 test"), () => isEnabled);
}
private bool isEnabled = false;
public RelayCommand EnDisable { get; set; }
public RelayCommand Action { get; set; }
}
Basically what happens here is that with the help of MEF, the ViewModel
gets a name "Demo1
" and can be found by using a CompositionContainer
like this:
[ImportMany("ViewModel", AllowRecomposition = true)]
private IEnumerable<Lazy<object, IViewModelMetadata>> ViewModels { get; set; }
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(typeof(ViewModelLocator).Assembly));
CompositionContainer _container = new CompositionContainer(catalog);
var compositionContainer = new CompositionContainer(catalog);
compositionContainer.ComposeParts(this);
The big advantage of this approach is that there is no maintenance of the link between the View
and the ViewModel
other than this, the locator is generic for all the ViewModel
s in your project! The TryGetMember
will then look like:
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
string name = binder.Name;
if (!dictionary.TryGetValue(name, out result))
try
{
if (ViewModels == null)
{
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog
(typeof(ViewModelLocator).Assembly));
CompositionContainer _container =
new CompositionContainer(catalog);
var compositionContainer =
new CompositionContainer(catalog);
compositionContainer.ComposeParts(this);
}
dictionary[binder.Name] = (result = ViewModels.Single
(v => v.Metadata.Name.Equals(name)).Value);
return result != null;
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
return true;
}
Designtime Binding
There are a lot of Locators available which can also handle the same functionality like my locator but most of those have problems with blendability where the properties of the ViewModel
do not show up in Blend or VS2010. Since I like that a lot, I tried to add some stuff I had used back in the days where all the database classes we had were DataSet
and DataTable
. The ITypedList
is used to specify which properties are 'not really there' - see: Implementing ITypedList for Virtual Properties. Together with the TypeDescriptionProvider
which can be used to support dynamic run-time properties, all the ViewModel
s which are created this way can be bindable.
Points of Interest
I learned a lot by reading all the various articles about MVVM. I hope this article can help others.
History
- 6th October, 2010: First version