Silverlight4: iniettare il ViewModel in una vista con l’utilizzo di MEF
Qualche giorno fa un certo Alessandro Scardova ha postato un link ad un articolo di un certo Thomas Martinsen, che mostrava come caricare l’istanza del ViewModel attraverso il Managed Extensibility Framework (per gli amici MEF). La cosa è sicuramente interessante, perchè non andiamo più a “cablare†il fatto che sulla MainPage.xaml deve essere utilizzata la classe MainViewModel. Questa dipendenza viene infatti iniettata, attraverso l’utilizzo degli attributi [Import] da una parte, ed [Export] dall’altra. Questi due attributi sono contenuti nell’assembly System.ComponentModel.Composition.dll, installato automaticamente – ricordiamolo – con il .NET Framework 4.
La soluzione, ripeto, è interessante, ma non mi piace più di tanto, perchè (cito dall’articolo originale di Thomas) “In the code-behind of the MainPage.xaml file hosting the view, I have created a property called ViewModel.†Insomma, è necessario aggiungere una proprietà pubblica della classe MainPage, agendo quindi sul file MainPage.xaml.cs, ovvero il code-behind, e quindi vanificando un po’ lo sforzo di adottare M-V-VM. Non voglio fare il fanatico, se c’è da toccare il code-behind facciamolo pure, ma consci del fatto che potrebbero correre dei rischi se il file viene toccato da altre persone, che magari per un motivo o per l’altro cancellano il nostro codice.
Ma la soluzione ovviamente c’è, e sono lieto di proporvela.
L’idea che ho avuto è quello di creare una classe ViewModelLocator, definita come segue:
public class ViewModelLocator
{
private MainViewModel _mainViewModel;
public ViewModelLocator()
{
CompositionInitializer.SatisfyImports(this);
}
[Import]
public MainViewModel Main
{
get { return _mainViewModel; }
set { _mainViewModel = value; }
}
public void ClearMain()
{
_mainViewModel.Cleanup();
_mainViewModel = null;
}
public void Cleanup()
{
ClearMain();
}
}
Non è tutta farina del mio sacco. Ho trascorso questa domenica pomeriggio a studiare il MVVM Light Tookit, di un certo Laurent Bugnion. Questa classe ViewModelLocator è la classe che vi trovate quando create un nuovo progetto usando questo tookit. Io l’ho solo ripulita un po’ apposta per questo esempio. Lo scopo di questa classe è quello di esporre – tramite public properties – tutti i ViewModel necessari al nostro progetto. Data la semplicità , in questo momento il ViewModel è uno solo, MainViewModel, ed è marcato con l’attributo [Import]. Questo significa che l’engine di MEF dovrà cercare ed iniettare la dipendenza, e lo fa con la chiamata a CompositionInitializer.SatisfyImports(this) che vedete nel costruttore della classe stessa.
A questo, basta andare nell’App.xaml ed aggiungere nelle Resources un’istanza di ViewModelLocator, come ho fatto qui sotto:
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MvvmLight1.App"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:MvvmLight1.Services;assembly=MvvmLight1.Services"
mc:Ignorable="d">
<Application.Resources>
<vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
</Application.Resources>
</Application>
Ho dichiarato il prefix “vm†ed ho istanziato ViewModelLocator.
A questo punto rimane solo un’ultima cosa: creare il MainViewModel vero e proprio, che decoreremo con l’attributo [Export] per informare l’engine di MEF. Supponiamo di scrivere quanto segue:
[Export(typeof(MainViewModel))]
public class MainViewModel : ViewModelBase
{
public string Welcome
{
get
{
return "Welcome to MVVM Light";
}
}
public MainViewModel()
{
if (IsInDesignMode)
{
// Code runs in Blend --> create design time data.
}
else
{
// Code runs "for real"
}
}
}
Questa è una normalissima classe ViewModel, che eredita da GalaSoft.MvvmLight.ViewModelBase. Notate – appunto – l’aggiunta dell’attributo [Export]. A questo punto, cosa accade? Accade che questo [Export] e l’[Import] messo sul ViewModelLocator si accoppiano fra loro, e MEF è quindi in grado di risolvere la dipendenza. Notate la presenza della public property Welcome sul ViewModel: è di tipo stringa, quindi possiamo bindarla ad una normale TextBlock sulla UI.
Consiglio dell’ultimo minuto : provate a mettere un breakpoint sul costruttore del ViewModelLocator, dove viene chiamato CompositionInitializer.SatisfyImports(), cioè la risoluzione delle composizioni da parte di MEF. Vedrete che prima della chiamata this.Main è null, mentre dopo la chiamata this.Main vale l’istanza del ViewModel marcata con Export.
Ed il tutto senza toccare il code-behind dello UserControl o della Page!!!
Questo sì che è vero MVVM!!!!