In letzter Zeit habe ich mich damit beschäftigt wie man effizient Views und Viewmodels in Silverlight verbinden könnte, da ich immer wieder mit komplizierten bis extrem komplizierten Mechanismen in Projekten konfrontiert wurde. Mein erster Ansatz war das Zuweisen des DataContexts in der View, wie es in vielen Blogs und Beispielen beschrieben ist.
<UserControl.DataContext>
<MEFedMVVM:MainPageViewModel/>
</UserControl.DataContext>
Das ist allerdings statisch und nicht elegant und unter Unittest Aspekten eine Katastrophe. Meine nächste Idee war, es mit MEF und mit dem ServiceLocator aus Microsoft Patterns & Practises das ViewModel über den Typ zu discovern. Der ServiceLocator kann unter http://commonservicelocator.codeplex.com/ downgeloadet werden. Im nächsten Schritt muss ein Service Provider für MEF geschrieben werden, welcher die MEF Exports auflistet und den richtigen Typen auflöst.
public class MefServiceLocator : ServiceLocatorImplBase
{
private readonly ExportProvider _provider;
public MefServiceLocator(ExportProvider provider)
{
_provider = provider;
}
protected override object DoGetInstance(Type serviceType, string key)
{
if (key == null)
{
key = AttributedModelServices.GetContractName(serviceType);
}
var exports = _provider.GetExports<object>(key);
if (exports.Any())
{
return exports.First().Value;
}
throw new ActivationException(string.Format("The Type {0} could not be resolved!", key));
}
protected override IEnumerable<object> DoGetAllInstances(Type serviceType)
{
return new List<object> { Activator.CreateInstance(serviceType) };
}
}
Mit dieser Klasse ist es nun möglich über diesen Aufruf ein ViewModel (oder einen beliebigen anderen Typen) zu discovern
ServiceLocator.Current.GetService(Type.GetType("MyNamespace.MyViewModel"));
So ist das schon deutlich eleganter und testbar, denn dem ServiceLocator kann man für Unittests bzw. für Coded UI Tests einen anderen ServiceLocator zuweisen und schon kann ein anderes ViewModel zurückgegeben werden, welches gar nicht dem angeforderten Typ entspricht. Um das ViewModel der View zuzuweisen fehlt allerdings noch ein Schritt, denn den oben genannten Aufruf bekommt man so nicht ins XAML und Code Behind ist an dieser Stelle auch nicht wirklich elegant. Um dieses Problem zu lösen habe ich mich wieder der Behaviour bedient.
public class ViewModelLocatingBehavior : Behavior
{
public string ViewModelName { get; set; }
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.DataContext = ServiceLocator.Current.GetService(Type.GetType(ViewModelName));
}
}
Diese Behaviour kann ins XAML eingefügt werden und ist unserem Ausgangscode sehr ähnlich.
<i:Interaction.Behaviors>
<MEFedMVVM:ViewModelLocatingBehavior ViewModelName="MEFedMVVM.MainPageViewModel"/>
</i:Interaction.Behaviors>
Die Initialisierung des ServiceLocators erfolgt im Startup Code der App.xaml.cs

Schematische Darstellung der MEF/ServiceLocator Architektur
Somit ist das Ziel erreicht und das ViewModel wird automatisch via MEF Export und ServiceLocator zugewiesen. Noch ein kleiner Tipp zum Schluss, den ersten Ansatz habe ich auskommentiert im XAML gelassen, da der Designer mit dem ServiceLocator und MEF Ansatz nichts anzeigt. Braucht man im Desiner Daten (z.B. Gridspalten) dann wird das Code-Fragment einkommentiert. Die beiden Ansätze beissen sich im Designer nicht.
Um das Beispiel in Gänze nachzuvollziehen, können Sie sich das Visual Studio Projekt hier downloaden.

