Come gestire l'inserimento delle dipendenze in un'applicazione WPF / MVVM


103

Sto avviando una nuova applicazione desktop e voglio crearla utilizzando MVVM e WPF.

Ho anche intenzione di utilizzare TDD.

Il problema è che non so come dovrei usare un container IoC per iniettare le mie dipendenze dal mio codice di produzione.

Supponiamo di avere la seguente classe e interfaccia:

public interface IStorage
{
    bool SaveFile(string content);
}

public class Storage : IStorage
{
    public bool SaveFile(string content){
        // Saves the file using StreamWriter
    }
}

E poi ho un'altra classe che ha IStoragecome dipendenza, supponiamo anche che questa classe sia un ViewModel o una business class ...

public class SomeViewModel
{
    private IStorage _storage;

    public SomeViewModel(IStorage storage){
        _storage = storage;
    }
}

Con questo posso scrivere facilmente unit test per assicurarmi che funzionino correttamente, usando mock ed ecc.

Il problema è quando si tratta di usarlo nell'applicazione reale. So che devo avere un contenitore IoC che collega un'implementazione predefinita per l' IStorageinterfaccia, ma come lo farei?

Ad esempio, come sarebbe se avessi il seguente xaml:

<Window 
    ... xmlns definitions ...
>
   <Window.DataContext>
        <local:SomeViewModel />
   </Window.DataContext>
</Window>

Come posso "dire" correttamente a WPF di iniettare le dipendenze in quel caso?

Inoltre, supponiamo di aver bisogno di un'istanza di SomeViewModeldal mio codice C #, come dovrei farlo?

Sento di essermi completamente perso in questo, apprezzerei qualsiasi esempio o guida su come sia il modo migliore per gestirlo.

Conosco StructureMap, ma non sono un esperto. Inoltre, se esiste un framework migliore / più facile / pronto all'uso, fammelo sapere.


Con .net core 3.0 in anteprima puoi farlo con alcuni pacchetti Microsoft nuget.
Bailey Miller

Risposte:


88

Sto usando Ninject e ho scoperto che è un piacere lavorarci. Tutto è impostato in codice, la sintassi è abbastanza semplice e ha una buona documentazione (e molte risposte su SO).

Quindi fondamentalmente funziona così:

Crea il modello di visualizzazione e prendi l' IStorageinterfaccia come parametro del costruttore:

class UserControlViewModel
{
    public UserControlViewModel(IStorage storage)
    {

    }
}

Crea un ViewModelLocatorcon una proprietà get per il modello di visualizzazione, che carica il modello di visualizzazione da Ninject:

class ViewModelLocator
{
    public UserControlViewModel UserControlViewModel
    {
        get { return IocKernel.Get<UserControlViewModel>();} // Loading UserControlViewModel will automatically load the binding for IStorage
    }
}

Crea ViewModelLocatoruna risorsa a livello di applicazione in App.xaml:

<Application ...>
    <Application.Resources>
        <local:ViewModelLocator x:Key="ViewModelLocator"/>
    </Application.Resources>
</Application>

Associa il DataContextdi UserControlalla proprietà corrispondente in ViewModelLocator.

<UserControl ...
             DataContext="{Binding UserControlViewModel, Source={StaticResource ViewModelLocator}}">
    <Grid>
    </Grid>
</UserControl>

Crea una classe che eredita NinjectModule, che configurerà le associazioni necessarie ( IStoragee il viewmodel):

class IocConfiguration : NinjectModule
{
    public override void Load()
    {
        Bind<IStorage>().To<Storage>().InSingletonScope(); // Reuse same storage every time

        Bind<UserControlViewModel>().ToSelf().InTransientScope(); // Create new instance every time
    }
}

Inizializza il kernel IoC all'avvio dell'applicazione con i moduli Ninject necessari (quello sopra per ora):

public partial class App : Application
{       
    protected override void OnStartup(StartupEventArgs e)
    {
        IocKernel.Initialize(new IocConfiguration());

        base.OnStartup(e);
    }
}

Ho usato una IocKernelclasse statica per contenere l'istanza a livello di applicazione del kernel IoC, quindi posso accedervi facilmente quando necessario:

public static class IocKernel
{
    private static StandardKernel _kernel;

    public static T Get<T>()
    {
        return _kernel.Get<T>();
    }

    public static void Initialize(params INinjectModule[] modules)
    {
        if (_kernel == null)
        {
            _kernel = new StandardKernel(modules);
        }
    }
}

Questa soluzione fa uso di una statica ServiceLocator(the IocKernel), che è generalmente considerata come un anti-pattern, perché nasconde le dipendenze della classe. Tuttavia è molto difficile evitare una sorta di ricerca manuale del servizio per le classi dell'interfaccia utente, poiché devono avere un costruttore senza parametri e non è comunque possibile controllare l'istanza, quindi non è possibile iniettare la VM. Almeno in questo modo è possibile testare la VM in isolamento, che è dove si trova tutta la logica aziendale.

Se qualcuno ha un modo migliore, per favore condividi.

EDIT: Lucky Likey ha fornito una risposta per sbarazzarsi del localizzatore di servizi statici, consentendo a Ninject di istanziare le classi dell'interfaccia utente. I dettagli della risposta possono essere visti qui


13
Sono nuovo nell'iniezione di dipendenze, ma nel suo cuore la tua soluzione sta combinando l'anti-pattern Service Locator con Ninject poiché stai usando il ViewModel Locator statico. Si potrebbe sostenere che l'iniezione viene eseguita nel file Xaml, che è meno probabile che venga testato. Non ho una soluzione migliore e probabilmente userò la tua, ma penso che sarebbe utile menzionarla anche nella risposta.
user3141326

L'uomo la soluzione è semplicemente fantastico, c'è solo un "problema" con la seguente riga: DataContext="{Binding [...]}". Questo fa sì che VS-Designer esegua tutto il codice di programma nel Costruttore di ViewModel. Nel mio caso la finestra viene eseguita e blocca in modo modale qualsiasi interazione con VS. Forse si dovrebbe modificare ViewModelLocator per non individuare i ViewModels "reali" in Design-Time. - Un'altra soluzione è "Disabilitare il codice del progetto", che impedirà anche che tutto il resto venga mostrato. Forse hai già trovato una soluzione chiara a questo. In questo caso ti prego di mostrarlo.
LuckyLikey

@LuckyLikey Puoi provare a usare d: DataContext = "{d: DesignInstance vm: UserControlViewModel, IsDesignTimeCreatable = True}" ma non sono sicuro che faccia la differenza. Ma perché / come il costruttore della VM sta lanciando una finestra modale? E che tipo di finestra?
sondergard

@son In realtà non so perché e come, ma quando apro un Window Designer da Solution Explorer, mentre la nuova scheda viene aperta, la finestra viene visualizzata dal designer e la stessa finestra appare come se fosse il debug modale, ospitato in un nuovo processo al di fuori di VS "Micorosoft Visual Studio XAML Designer". Se il processo viene chiuso, anche VS-Designer fallisce con l'eccezione menzionata in precedenza. Proverò la tua soluzione alternativa. Ti
avviserò non appena rileverò

1
@sondergard Ho pubblicato un miglioramento alla tua risposta, evitando il ServiceLocator Anti-Pattern. Sentiti libero di dare un'occhiata.
LuckyLikey

52

Nella tua domanda imposti il ​​valore della DataContextproprietà della vista in XAML. Ciò richiede che il tuo modello di visualizzazione abbia un costruttore predefinito. Tuttavia, come hai notato, questo non funziona bene con l'inserimento delle dipendenze in cui vuoi iniettare le dipendenze nel costruttore.

Quindi non puoi impostare la DataContextproprietà in XAML . Invece hai altre alternative.

Se la tua applicazione è basata su un semplice modello di visualizzazione gerarchico, puoi costruire l'intera gerarchia del modello di visualizzazione all'avvio dell'applicazione (dovrai rimuovere la StartupUriproprietà dal App.xamlfile):

public partial class App {

  protected override void OnStartup(StartupEventArgs e) {
    base.OnStartup(e);
    var container = CreateContainer();
    var viewModel = container.Resolve<RootViewModel>();
    var window = new MainWindow { DataContext = viewModel };
    window.Show();
  }

}

Questo si basa su un grafico a oggetti di modelli di visualizzazione radicati in RootViewModelma è possibile iniettare alcune fabbriche di modelli di visualizzazione nei modelli di visualizzazione padre consentendo loro di creare nuovi modelli di visualizzazione figlio in modo che il grafico a oggetti non debba essere corretto. Spero che questo risponda anche alla tua domanda, supponiamo di aver bisogno di un'istanza di SomeViewModeldal mio cscodice, come dovrei farlo?

class ParentViewModel {

  public ParentViewModel(ChildViewModelFactory childViewModelFactory) {
    _childViewModelFactory = childViewModelFactory;
  }

  public void AddChild() {
    Children.Add(_childViewModelFactory.Create());
  }

  ObservableCollection<ChildViewModel> Children { get; private set; }

 }

class ChildViewModelFactory {

  public ChildViewModelFactory(/* ChildViewModel dependencies */) {
    // Store dependencies.
  }

  public ChildViewModel Create() {
    return new ChildViewModel(/* Use stored dependencies */);
  }

}

Se la tua applicazione è di natura più dinamica e forse è basata sulla navigazione dovrai agganciarti al codice che esegue la navigazione. Ogni volta che si passa a una nuova visualizzazione è necessario creare un modello di visualizzazione (dal contenitore DI), la visualizzazione stessa e impostare DataContextla visualizzazione del modello di visualizzazione. Puoi eseguire prima questa vista quando scegli un modello di visualizzazione basato su una vista oppure puoi farlo prima modello di visualizzazionedove il modello di visualizzazione determina quale visualizzazione utilizzare. Un framework MVVM fornisce questa funzionalità chiave con un modo per agganciare il tuo contenitore DI alla creazione di modelli di visualizzazione, ma puoi anche implementarlo tu stesso. Sono un po 'vago qui perché a seconda delle tue esigenze questa funzionalità potrebbe diventare piuttosto complessa. Questa è una delle funzioni principali che ottieni da un framework MVVM, ma eseguirne uno tuo in una semplice applicazione ti darà una buona comprensione di cosa forniscono i framework MVVM sotto il cofano.

Non potendo dichiarare DataContextin XAML si perde un po 'di supporto in fase di progettazione. Se il tuo modello di visualizzazione contiene alcuni dati, apparirà durante la fase di progettazione, il che può essere molto utile. Fortunatamente, puoi usare gli attributi della fase di progettazione anche in WPF. Un modo per eseguire questa operazione consiste nell'aggiungere i seguenti attributi <Window>all'elemento o <UserControl>in XAML:

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:MyViewModel, IsDesignTimeCreatable=True}"

Il tipo di modello di visualizzazione dovrebbe avere due costruttori, quello predefinito per i dati in fase di progettazione e un altro per l'inserimento delle dipendenze:

class MyViewModel : INotifyPropertyChanged {

  public MyViewModel() {
    // Create some design-time data.
  }

  public MyViewModel(/* Dependencies */) {
    // Store dependencies.
  }

}

In questo modo è possibile utilizzare l'inserimento delle dipendenze e mantenere un buon supporto in fase di progettazione.


13
Questo e 'esattamente quello che stavo cercando. Mi frustra quante volte leggo risposte che dicono "Usa il framework [ yadde-ya ]". E 'tutto molto bello, ma voglio sapere esattamente come rotolare questo me stesso prima e poi posso sapere che tipo di quadro potrebbe effettivamente essere utile per me. Grazie per averlo spiegato così chiaramente.
kmote

28

Quello che sto postando qui è un miglioramento della risposta di sondergard, perché quello che sto per raccontare non rientra in un commento :)

In effetti sto introducendo una soluzione pulita, che evita la necessità di un ServiceLocator e di un wrapper per l' StandardKernelistanza, che in Sondergard's Solution si chiama IocContainer. Perché? Come accennato, quelli sono anti-modelli.

Rendere StandardKerneldisponibile ovunque

La chiave della magia di Ninject è l' StandardKernelistanza necessaria per utilizzare il .Get<T>()metodo.

In alternativa a sondergard IocContainerpuoi creare l' StandardKernelinterno della App-Class.

Basta rimuovere StartUpUri dal tuo App.xaml

<Application x:Class="Namespace.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
             ... 
</Application>

Questo è il CodeBehind dell'app all'interno di App.xaml.cs

public partial class App
{
    private IKernel _iocKernel;

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        _iocKernel = new StandardKernel();
        _iocKernel.Load(new YourModule());

        Current.MainWindow = _iocKernel.Get<MainWindow>();
        Current.MainWindow.Show();
    }
}

D'ora in poi, Ninject è vivo e pronto a combattere :)

Iniettando il tuo DataContext

Dato che Ninject è vivo, puoi eseguire tutti i tipi di iniezioni, ad es. Property Setter Injection o la più comune Constructor Injection .

Questo è come si inietta il tuo ViewModel nelle vostre Window'sDataContext

public partial class MainWindow : Window
{
    public MainWindow(MainWindowViewModel vm)
    {
        DataContext = vm;
        InitializeComponent();
    }
}

Ovviamente puoi anche iniettare un IViewModelse esegui gli attacchi giusti, ma questa non fa parte di questa risposta.

Accesso diretto al kernel

Se hai bisogno di chiamare i metodi direttamente sul kernel (ad esempio .Get<T>()-Method), puoi lasciare che il kernel si inietti da solo.

    private void DoStuffWithKernel(IKernel kernel)
    {
        kernel.Get<Something>();
        kernel.Whatever();
    }

Se avessi bisogno di un'istanza locale del kernel potresti iniettarla come Property.

    [Inject]
    public IKernel Kernel { private get; set; }

Anche se questo può essere piuttosto utile, non ti consiglierei di farlo. Tieni presente che gli oggetti iniettati in questo modo non saranno disponibili all'interno del Constructor, perché vengono iniettati in seguito.

Secondo questo link dovresti usare l'estensione di fabbrica invece di iniettare il IKernel(contenitore DI).

L'approccio consigliato per utilizzare un contenitore DI in un sistema software è che la radice di composizione dell'applicazione sia l'unico punto in cui il contenitore viene toccato direttamente.

Anche il modo in cui deve essere utilizzato Ninject.Extensions.Factory può essere rosso qui .


Bel approccio. Non ho mai esplorato Ninject a questo livello, ma vedo che mi sto perdendo :)
sondergard

@son thx. Alla fine della tua risposta hai affermato se qualcuno ha un modo migliore, per favore condividi. Potresti aggiungere un link a questo?
LuckyLikey

se qualcuno è interessato a come usarlo Ninject.Extensions.Factorysu questo, dichiaralo qui nei commenti e aggiungerò qualche informazione in più.
LuckyLikey

1
@LuckyLikey: come potrei aggiungere un ViewModel a un contesto di dati della finestra tramite XAML che non ha un costruttore senza parametri? Con la soluzione di sondergard con ServiceLocator questa situazione sarebbe possibile.
Thomas Geulen

Quindi per favore dimmi come recuperare i servizi di cui ho bisogno nelle proprietà allegate? Sono sempre statici, sia il DependencyPropertycampo di supporto che i suoi metodi Get e Set.
Springy76

12

Vado per un approccio "view first", in cui passo il modello di visualizzazione al costruttore della vista (nel suo code-behind), che viene assegnato al contesto dei dati, ad es.

public class SomeView
{
    public SomeView(SomeViewModel viewModel)
    {
        InitializeComponent();

        DataContext = viewModel;
    }
}

Questo sostituisce il tuo approccio basato su XAML.

Uso il framework Prism per gestire la navigazione: quando del codice richiede la visualizzazione di una vista particolare ("navigando" su di essa), Prism risolverà quella vista (internamente, utilizzando il framework DI dell'app); il framework DI a sua volta risolverà tutte le dipendenze della vista (il modello di vista nel mio esempio), quindi risolverà le sue dipendenze e così via.

La scelta del framework DI è praticamente irrilevante in quanto fanno tutti essenzialmente la stessa cosa, cioè si registra un'interfaccia (o un tipo) insieme al tipo concreto che si desidera che il framework installi quando trova una dipendenza da quell'interfaccia. Per la cronaca uso Castle Windsor.

La navigazione con prisma richiede un po 'di tempo per abituarsi, ma è abbastanza buona una volta che ci si capisce bene, permettendoti di comporre la tua applicazione utilizzando visualizzazioni diverse. Ad esempio, potresti creare una "regione" Prism nella finestra principale, quindi utilizzando la navigazione Prism passeresti da una vista all'altra all'interno di questa regione, ad esempio quando l'utente seleziona le voci di menu o qualsiasi altra cosa.

In alternativa, dai un'occhiata a uno dei framework MVVM come MVVM Light. Non ho esperienza di questi quindi non posso commentare su come si usano.


1
Come si passano gli argomenti del costruttore alle visualizzazioni figlio? Ho provato questo approccio, ma ho delle eccezioni nella vista genitore che mi dice che la vista figlio non ha un costruttore senza parametri predefinito
Doctor Jones

10

Installa MVVM Light.

Parte dell'installazione consiste nel creare un localizzatore di modelli di visualizzazione. Questa è una classe che espone i tuoi viewmodels come proprietà. Il getter di queste proprietà può quindi essere restituito istanze dal tuo motore IOC. Fortunatamente, MVVM light include anche il framework SimpleIOC, ma puoi cablare altri se lo desideri.

Con semplice IOC registri un'implementazione su un tipo ...

SimpleIOC.Default.Register<MyViewModel>(()=> new MyViewModel(new ServiceProvider()), true);

In questo esempio, il tuo modello di visualizzazione viene creato e passato a un oggetto del fornitore di servizi secondo il suo costruttore.

Quindi crei una proprietà che restituisce un'istanza da IOC.

public MyViewModel
{
    get { return SimpleIOC.Default.GetInstance<MyViewModel>; }
}

La parte intelligente è che il localizzatore del modello di visualizzazione viene quindi creato in app.xaml o equivalente come origine dati.

<local:ViewModelLocator x:key="Vml" />

Ora puoi eseguire il binding alla sua proprietà 'MyViewModel' per ottenere il tuo viewmodel con un servizio iniettato.

Spero che aiuti. Ci scusiamo per eventuali inesattezze del codice, codificato dalla memoria su un iPad.


Non dovresti avere un GetInstanceo resolveal di fuori del bootstrap dell'applicazione. Questo è il punto di DI!
Soleil - Mathieu Prévot

Sono d'accordo che è possibile impostare il valore della proprietà durante l'avvio, ma suggerire che l'utilizzo di istanze pigre è contro DI è sbagliato.
kidshaw

@kishaw non l'ho fatto.
Soleil - Mathieu Prévot

3

Custodia Canonic DryIoc

Rispondere a un vecchio post, ma farlo con DryIoce fare quello che penso sia un buon uso di DI e interfacce (uso minimo di classi concrete).

  1. Il punto di partenza di un'app WPF è App.xaml, e lì diciamo qual è la vista iniziale da utilizzare; lo facciamo con il codice dietro invece del xaml predefinito:
  2. rimuovere StartupUri="MainWindow.xaml"in App.xaml
  3. in codebehind (App.xaml.cs) aggiungi questo override OnStartup:

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        DryContainer.Resolve<MainWindow>().Show();
    }

questo è il punto di partenza; è anche l'unico posto dove resolvedovrebbe essere chiamato.

  1. la radice di configurazione (secondo il libro di Mark Seeman Dependency injection in .NET; l'unico posto in cui le classi concrete dovrebbero essere menzionate) sarà nello stesso codebehind, nel costruttore:

    public Container DryContainer { get; private set; }
    
    public App()
    {
        DryContainer = new Container(rules => rules.WithoutThrowOnRegisteringDisposableTransient());
        DryContainer.Register<IDatabaseManager, DatabaseManager>();
        DryContainer.Register<IJConfigReader, JConfigReader>();
        DryContainer.Register<IMainWindowViewModel, MainWindowViewModel>(
            Made.Of(() => new MainWindowViewModel(Arg.Of<IDatabaseManager>(), Arg.Of<IJConfigReader>())));
        DryContainer.Register<MainWindow>();
    }

Osservazioni e qualche dettaglio in più

  • Ho usato la classe concreta solo con la vista MainWindow;
  • Ho dovuto specificare quale costruttore usare (dobbiamo farlo con DryIoc) per ViewModel, perché il costruttore predefinito deve esistere per il progettista XAML e il costruttore con iniezione è quello effettivamente utilizzato per l'applicazione.

Il costruttore ViewModel con DI:

public MainWindowViewModel(IDatabaseManager dbmgr, IJConfigReader jconfigReader)
{
    _dbMgr = dbmgr;
    _jconfigReader = jconfigReader;
}

ViewModel costruttore predefinito per la progettazione:

public MainWindowViewModel()
{
}

Il codice dietro la vista:

public partial class MainWindow
{
    public MainWindow(IMainWindowViewModel vm)
    {
        InitializeComponent();
        ViewModel = vm;
    }

    public IViewModel ViewModel
    {
        get { return (IViewModel)DataContext; }
        set { DataContext = value; }
    }
}

e cosa è necessario nella vista (MainWindow.xaml) per ottenere un'istanza di progettazione con ViewModel:

d:DataContext="{d:DesignInstance local:MainWindowViewModel, IsDesignTimeCreatable=True}"

Conclusione

Abbiamo quindi ottenuto un'implementazione molto pulita e minimale di un'applicazione WPF con un contenitore DryIoc e DI pur mantenendo possibili istanze di progettazione di viste e modelli di visualizzazione.


2

Usa il Managed Extensibility Framework .

[Export(typeof(IViewModel)]
public class SomeViewModel : IViewModel
{
    private IStorage _storage;

    [ImportingConstructor]
    public SomeViewModel(IStorage storage){
        _storage = storage;
    }

    public bool ProperlyInitialized { get { return _storage != null; } }
}

[Export(typeof(IStorage)]
public class Storage : IStorage
{
    public bool SaveFile(string content){
        // Saves the file using StreamWriter
    }
}

//Somewhere in your application bootstrapping...
public GetViewModel() {
     //Search all assemblies in the same directory where our dll/exe is
     string currentPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
     var catalog = new DirectoryCatalog(currentPath);
     var container = new CompositionContainer(catalog);
     var viewModel = container.GetExport<IViewModel>();
     //Assert that MEF did as advertised
     Debug.Assert(viewModel is SomViewModel); 
     Debug.Assert(viewModel.ProperlyInitialized);
}

In generale, quello che faresti è avere una classe statica e utilizzare il Factory Pattern per fornirti un contenitore globale (memorizzato nella cache, natch).

Per quanto riguarda come iniettare i modelli di visualizzazione, li inietti nello stesso modo in cui inietti tutto il resto. Crea un costruttore di importazione (o inserisci un'istruzione di importazione su una proprietà / campo) nel code-behind del file XAML e chiedigli di importare il modello di visualizzazione. Quindi associare il vostro Window's DataContextper quella proprietà. Gli oggetti radice che effettivamente tiri fuori dal contenitore sono solitamente Windowoggetti composti . Basta aggiungere le interfacce alle classi della finestra ed esportarle, quindi prelevarle dal catalogo come sopra (in App.xaml.cs ... questo è il file bootstrap di WPF).


Ti manca un punto importante di DI che è evitare qualsiasi creazione di istanze con new.
Soleil - Mathieu Prévot

0

Suggerirei di utilizzare ViewModel - Primo approccio https://github.com/Caliburn-Micro/Caliburn.Micro

vedere: https://caliburnmicro.codeplex.com/wikipage?title=All%20About%20Conventions

utilizzare Castle Windsorcome contenitore IOC.

Tutto sulle convenzioni

Una delle caratteristiche principali di Caliburn.Micro si manifesta nella sua capacità di eliminare la necessità del codice targa agendo su una serie di convenzioni. Alcune persone amano le convenzioni e altre le odiano. Ecco perché le convenzioni di CM sono completamente personalizzabili e possono anche essere disattivate completamente se non lo si desidera. Se intendi utilizzare le convenzioni e poiché sono attive per impostazione predefinita, è bene sapere quali sono queste convenzioni e come funzionano. Questo è l'argomento di questo articolo. Visualizza risoluzione (ViewModel-First)

Nozioni di base

La prima convenzione che potresti incontrare quando utilizzi CM è relativa alla risoluzione della vista. Questa convenzione influisce su tutte le aree ViewModel-First dell'applicazione. In ViewModel-First, abbiamo un ViewModel esistente che dobbiamo visualizzare sullo schermo. A tale scopo, CM utilizza un semplice modello di denominazione per trovare un UserControl1 da associare a ViewModel e visualizzare. Allora, qual è questo schema? Diamo solo un'occhiata a ViewLocator.LocateForModelType per scoprirlo:

public static Func<Type, DependencyObject, object, UIElement> LocateForModelType = (modelType, displayLocation, context) =>{
    var viewTypeName = modelType.FullName.Replace("Model", string.Empty);
    if(context != null)
    {
        viewTypeName = viewTypeName.Remove(viewTypeName.Length - 4, 4);
        viewTypeName = viewTypeName + "." + context;
    }

    var viewType = (from assmebly in AssemblySource.Instance
                    from type in assmebly.GetExportedTypes()
                    where type.FullName == viewTypeName
                    select type).FirstOrDefault();

    return viewType == null
        ? new TextBlock { Text = string.Format("{0} not found.", viewTypeName) }
        : GetOrCreateViewType(viewType);
};

Inizialmente ignoriamo la variabile "contesto". Per derivare la vista, supponiamo che tu stia utilizzando il testo "ViewModel" nella denominazione delle tue VM, quindi lo cambiamo semplicemente in "Visualizza" ovunque lo troviamo rimuovendo la parola "Modello". Ciò ha l'effetto di modificare sia i nomi dei tipi che gli spazi dei nomi. Quindi ViewModels.CustomerViewModel diventerebbe Views.CustomerView. Oppure, se stai organizzando la tua applicazione per funzionalità: CustomerManagement.CustomerViewModel diventa CustomerManagement.CustomerView. Si spera che sia abbastanza semplice. Una volta ottenuto il nome, cerchiamo i tipi con quel nome. Cerchiamo qualsiasi assembly che hai esposto a CM come ricercabile tramite AssemblySource.Instance.2 Se troviamo il tipo, creiamo un'istanza (o ne prendiamo una dal contenitore IoC se è registrato) e la restituiamo al chiamante. Se non troviamo il tipo,

Ora, torniamo a quel valore di "contesto". Ecco come CM supporta più viste sullo stesso ViewModel. Se viene fornito un contesto (tipicamente una stringa o un'enumerazione), eseguiamo un'ulteriore trasformazione del nome, in base a quel valore. Questa trasformazione presume effettivamente che tu abbia una cartella (spazio dei nomi) per le diverse viste rimuovendo la parola "Visualizza" dalla fine e aggiungendo invece il contesto. Quindi, dato un contesto di "Master", il nostro ViewModels.CustomerViewModel diventerebbe Views.Customer.Master.


2
Il tuo intero post è un'opinione.
John Peters

-1

Rimuovi l'uri di avvio dal tuo app.xaml.

App.xaml.cs

public partial class App
{
    protected override void OnStartup(StartupEventArgs e)
    {
        IoC.Configure(true);

        StartupUri = new Uri("Views/MainWindowView.xaml", UriKind.Relative);

        base.OnStartup(e);
    }
}

Ora puoi usare la tua classe IoC per costruire le istanze.

MainWindowView.xaml.cs

public partial class MainWindowView
{
    public MainWindowView()
    {
        var mainWindowViewModel = IoC.GetInstance<IMainWindowViewModel>();

        //Do other configuration            

        DataContext = mainWindowViewModel;

        InitializeComponent();
    }

}

Non dovresti avere alcun contenitore GetInstancedi resolveapp.xaml.cs esterno, stai perdendo il punto di DI. Inoltre, menzionare la vista xaml nel codice dietro della vista è un po 'contorto. Basta chiamare la visualizzazione in puro c # e farlo con il contenitore.
Soleil - Mathieu Prévot
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.