In un architettura basata su Model-View-ViewModel (MVVM) spesso è necessario ‘iniettare’ nel ViewModel delle entities di supporto al ViewModel stesso, ad esempio, in un ottica ViewModel-First è consuetudine passare nel costruttore una generica IView in modo che il ViewModel possa poi comunicare facilmente con la view stessa.
L’utilizzo di un interfaccia fa si che questo approccio non comprometta la testabilità del ViewModel in quanto l’interfaccia è facilmente ‘mockabile’ ma Il fatto di avere un ViewModel che non ha un costruttore pubblico senza parametri compromette un altro aspetto al quale è difficile rinunciare: Il supporto a design time da parte di Expression Blend (a.k.a Blendability), fortunatamente esiste un workaround che sopperisce a questo problema.

Partiamo da un  ViewModel fatto in questo modo (l’esempio è basato su Silverlight, ma è comunque applicabile a WPF)

   1: public class MainPageViewModel
   2: {
   3:     IUnityContainer container;
   4:     private static bool? _isInDesignMode;
   5:  
   6:     public MainPageViewModel(IUnityContainer container)
   7:     {
   8:         this.container = container;
   9:         if(this.GetIsInDesignMode()) this.Name = "Corrado";
  10:     }
  11:  
  12:     public void DoMessageBox()
  13:     {
  14:         this.container.Resolve<IView>().ShowMessageBox();
  15:     }
  16:  
  17:     public string Name { get; set; }
  18:  
  19:     //This should reside in a static class indeed...
  20:     public bool GetIsInDesignMode()
  21:     {
  22:         if (!_isInDesignMode.HasValue)
  23:         {
  24:             _isInDesignMode =
  25:                  (null == Application.Current) ||
  26:                  Application.Current.GetType() == typeof(Application);
  27:         }
  28:         return _isInDesignMode.Value;
  29:     }
  30: }

Dal codice si nota come il costruttore accetti un interfaccia IUnityContainer definita nel IOC container Unity di Microsoft, il motivo percui ho deciso di utilizzare un IOC container è perchè, effettivamente, in un ottica MVVM (e non solo…) è decisamente comodo: semplicemente passando il container posso risolvere tutte le dipendenze del mio ViewModel, presenti e future centralizzando la relativa definizione/creazione in un unico punto e delegando l’iniezione di eventuali dipendenze. Il Viewmodel espone inoltre una proprietà Name che viene valorizzata esclusivamente design time.

L’interfaccia IView utilizzata dal metodo DoMessageBox è banale:

   1: public interface IView
   2:     {
   3:         void ShowMessageBox();
   4:     }

Ed è implementata nel code-behind della View associata al ViewModel (per semplicità, il pulsante che visualizza la message box non fa uso di Command e tutto ciò che ne deriva…)

   1: public MainPage()
   2: {
   3:     InitializeComponent();
   4: }
   5:  
   6: public void ShowMessageBox()
   7: {
   8:     MessageBox.Show("Message...");
   9: }
  10:  
  11: private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
  12: {
  13:     (this.DataContext as MainPageViewModel).DoMessageBox();
  14: }        

A questo punto ci rimane da associare, come da manuale, un istanza di MainPageViewModel alla proprietà DataContext di MainPage, volendo però sfruttare la Blendability cioè la possibilità di avere un istanza del nostro view-model anche a design-time, l’unica possibilità è quella di associare il ViewModel direttamente nello XAML in questo modo:

   1: <UserControl x:Class="ViewModelFirst_Approach.MainPage"
   2:              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   4:              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   5:              xmlns:this="clr-namespace:ViewModelFirst_Approach"
   6:              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   7:              d:DesignHeight="480" d:DesignWidth="640" mc:Ignorable="d">
   8:     <UserControl.DataContext>
   9:         <this:MainPageViewModel />
  10:     </UserControl.DataContext>
  11:     <Grid x:Name="LayoutRoot">
  14:         <Button Width="85" Height="30" HorizontalAlignment="Left" Margin="125,208,0,0" VerticalAlignment="Top"
  15:                 Click="Button_Click" Content="Button" />
  16:     </Grid>
  17: </UserControl>

Sfortunatamente, la mancanza di un costruttore senza parametri non ci da la possibilità di scrivere:

   1: <UserControl.DataContext>
   2:   <this:MainPageViewModel />
   3: </UserControl.DataContext>

Dobbiamo perciò creare un oggetto che metta a disposizione delle istanze dei vari ViewModels pre-istanziate alle quali connetterci via dataBinding, nel mio caso ho chiamato questo oggetto ViewModelLocator:

   1: public class ViewModelLocator
   2: {
   3:     ContainerService containerService;
   4:  
   5:     public ViewModelLocator()
   6:     {
   7:         this.containerService = new ContainerService();
   8:     }
   9:  
  10:     public MainPageViewModel MainPageViewModel
  11:     {
  12:         get { return containerService.Container.Resolve<MainPageViewModel>(); }
  13:     }
  14: }

Il costruttore di ViewModelLocator crea un istanza della classe ContainerService la quale si occupa di inserire nel IOC container i vari tipi che verranno successivamente risolti:

   1: public class ContainerService
   2: {
   3:     private IUnityContainer unityContainer;
   4:  
   5:     public IUnityContainer Container
   6:     {
   7:         get { return this.unityContainer; }
   8:     }
   9:  
  10:     public ContainerService()
  11:     {
  12:         //Registers types inside IOC container
  13:         UnityContainer container = new UnityContainer();
  14:         container.RegisterType<ViewModelLocator>();
  15:         container.RegisterType<IView, MainPage>(new ContainerControlledLifetimeManager());
  16:         container.RegisterType<MainPageViewModel>();
  17:         unityContainer = container;        
  18:     }
  19: }

Ora creiamo un istanza di ViewModelLocator come risorsa di applicazione in App.xaml rendendo l’oggetto disponibile globalmente:

   1: <Application
   2:   x:Class="ViewModelFirst_Approach.App"
   3:   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   4:   xmlns:this="clr-namespace:ViewModelFirst_Approach"
   5:   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
   6:   <Application.Resources>
   7:     <this:ViewModelLocator x:Key="ViewModelLocator"/>
   8:   </Application.Resources>
   9: </Application>

Bindiamo il datacontext di MainPage alla proprietà MainPageViewModel della risorsa ViewModelLocator:

   1: <UserControl x:Class="ViewModelFirst_Approach.MainPage"
   2:              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   4:              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   5:              xmlns:this="clr-namespace:ViewModelFirst_Approach"
   6:              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   7:              d:DesignHeight="480" d:DesignWidth="640" mc:Ignorable="d"
   8:              DataContext="{Binding Path=MainPageViewModel, Source={StaticResource ViewModelLocator}}" >

Volendo utilizzare Expression Blend per aggiungere un TextBlock alla pagina, non solo si ha la finestra di Binding correttamente popolata:

 image

Ma a design time il TextBlock contiene il valore fittizio e questo, quanto dovete magari create un nuovo DataTemplate, non ha prezzo: smile_regular

image 

L’uso di Unity fa si che sia l’IOC container ad occuparsi di iniettare l’opportuna istanza di IUnityContainer nel nostro ViewModel (e non è poco…) la ‘scomodità’ sta nel doversi ricordare di aggiornare il ViewModelLocator ogni volta che si aggiunge un nuovo ViewModel che si vuole utilizzare a design-time.

L’approccio è comodo e funziona, non a caso è ampiamente utilizzato nel MVVM Toolkit di Laurent Bugnion che, al momento ritengo il miglior compromesso tra features e semplicità di utilizzo.