Comment gérer l'injection de dépendances dans une application WPF / MVVM

103

Je démarre une nouvelle application de bureau et je souhaite la créer à l'aide de MVVM et WPF.

J'ai également l'intention d'utiliser TDD.

Le problème est que je ne sais pas comment utiliser un conteneur IoC pour injecter mes dépendances sur mon code de production.

Supposons que j'ai la classe et l'interface suivantes:

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

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

Et puis j'ai une autre classe qui a IStoragepour dépendance, supposons aussi que cette classe soit un ViewModel ou une classe affaires ...

public class SomeViewModel
{
    private IStorage _storage;

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

Avec cela, je peux facilement écrire des tests unitaires pour m'assurer qu'ils fonctionnent correctement, en utilisant des simulations, etc.

Le problème est quand il s'agit de l'utiliser dans l'application réelle. Je sais que je dois avoir un conteneur IoC qui lie une implémentation par défaut pour l' IStorageinterface, mais comment ferais-je cela?

Par exemple, comment serait-ce si j'avais le xaml suivant:

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

Comment puis-je «dire» correctement à WPF d'injecter des dépendances dans ce cas?

De plus, supposons que j'ai besoin d'une instance de SomeViewModelde mon code C #, comment dois-je le faire?

Je sens que je suis complètement perdu dans tout cela, j'apprécierais tout exemple ou toute indication sur la meilleure façon de gérer cela.

Je connais StructureMap, mais je ne suis pas un expert. De plus, s'il existe un cadre meilleur / plus facile / prêt à l'emploi, veuillez me le faire savoir.

Fedaykin
la source
Avec .net core 3.0 en préversion, vous pouvez le faire avec certains packages Microsoft nuget.
Bailey Miller

Réponses:

88

J'utilise Ninject et j'ai trouvé que c'était un plaisir de travailler avec. Tout est mis en place dans le code, la syntaxe est assez simple et il a une bonne documentation (et beaucoup de réponses sur SO).

Donc, en gros, ça va comme ça:

Créez le modèle de vue et prenez l' IStorageinterface comme paramètre de constructeur:

class UserControlViewModel
{
    public UserControlViewModel(IStorage storage)
    {

    }
}

Créez un ViewModelLocatoravec une propriété get pour le modèle de vue, qui charge le modèle de vue à partir de Ninject:

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

Créez ViewModelLocatorune ressource à l'échelle de l'application dans App.xaml:

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

Liez le DataContextde UserControlà la propriété correspondante dans ViewModelLocator.

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

Créez une classe héritant de NinjectModule, qui mettra en place les liaisons nécessaires ( IStorageet le 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
    }
}

Initialisez le noyau IoC au démarrage de l'application avec les modules Ninject nécessaires (celui ci-dessus pour l'instant):

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

        base.OnStartup(e);
    }
}

J'ai utilisé une IocKernelclasse statique pour contenir l'instance du noyau IoC à l'échelle de l'application, afin de pouvoir y accéder facilement en cas de besoin:

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);
        }
    }
}

Cette solution utilise un static ServiceLocator(the IocKernel), qui est généralement considéré comme un anti-pattern, car il cache les dépendances de la classe. Cependant, il est très difficile d'éviter une sorte de recherche manuelle de services pour les classes d'interface utilisateur, car elles doivent avoir un constructeur sans paramètre et vous ne pouvez pas contrôler l'instanciation de toute façon, vous ne pouvez donc pas injecter la machine virtuelle. Au moins, cette méthode vous permet de tester la machine virtuelle de manière isolée, où se trouve toute la logique métier.

Si quelqu'un a un meilleur moyen, partagez-le.

EDIT: Lucky Likey a fourni une réponse pour se débarrasser du localisateur de service statique, en laissant Ninject instancier les classes d'interface utilisateur. Les détails de la réponse peuvent être consultés ici

sondergard
la source
13
Je suis nouveau dans l'injection de dépendances, mais dans son cœur votre solution combine l'anti-pattern Service Locator avec le Ninject puisque vous utilisez le ViewModel Locator statique. On pourrait dire que l'injection est effectuée dans un fichier Xaml, qui est moins susceptible d'être testé. Je n'ai pas de meilleure solution et j'utiliserai probablement la vôtre - mais je pense qu'il serait utile de le mentionner également dans la réponse.
user3141326
Man votre solution est tout simplement génial, il n'y a que l' un « problème » avec la ligne suivante: DataContext="{Binding [...]}". Cela oblige VS-Designer à exécuter tout le code de programme dans le constructeur du ViewModel. Dans mon cas, la fenêtre est exécutée et bloque modalement toute interaction avec VS. Peut-être devrait-on modifier le ViewModelLocator pour ne pas localiser les "vrais" ViewModels au moment du design. - Une autre solution consiste à "Désactiver le code du projet", ce qui empêchera également tout le reste d'être affiché. Peut-être avez-vous déjà trouvé une solution intéressante à cela. Dans ce cas, je vous ferais plaisir de le montrer.
LuckyLikey
@LuckyLikey Vous pouvez essayer d'utiliser d: DataContext = "{d: DesignInstance vm: UserControlViewModel, IsDesignTimeCreatable = True}" mais je ne suis pas sûr que cela fasse une différence. Mais pourquoi / comment le constructeur de VM lance-t-il une fenêtre modale? Et quel genre de fenêtre?
sondergard
@son En fait, je ne sais pas pourquoi et comment, mais lorsque j'ouvre un concepteur de fenêtres à partir de l'Explorateur de solutions, alors que le nouvel onglet est ouvert, la fenêtre est affichée par le concepteur et la même fenêtre apparaît comme si le débogage modal, hébergé dans un nouveau processus en dehors de VS "Micorosoft Visual Studio XAML Designer". Si le processus est arrêté, le VS-Designer échoue également avec l'exception mentionnée ci-dessus. Je vais essayer votre solution de contournement. Je vous avertirai dès que je détecte de nouvelles informations :)
LuckyLikey
1
@sondergard J'ai publié une amélioration de votre réponse, en évitant l'anti-pattern ServiceLocator. N'hésitez pas à y jeter un œil.
LuckyLikey
52

Dans votre question, vous définissez la valeur de la DataContextpropriété de la vue en XAML. Cela nécessite que votre modèle de vue ait un constructeur par défaut. Cependant, comme vous l'avez noté, cela ne fonctionne pas bien avec l'injection de dépendances où vous souhaitez injecter des dépendances dans le constructeur.

Donc , vous ne pouvez pas définir la DataContextpropriété en XAML . Au lieu de cela, vous avez d'autres alternatives.

Si votre application est basée sur un modèle de vue hiérarchique simple, vous pouvez construire la hiérarchie complète du modèle de vue au démarrage de l'application (vous devrez supprimer la StartupUripropriété du App.xamlfichier):

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();
  }

}

Ceci est basé sur un graphique d'objet de modèles de vue enraciné au niveau du, RootViewModelmais vous pouvez injecter des fabriques de modèles de vue dans des modèles de vue parents, ce qui leur permet de créer de nouveaux modèles de vue enfants afin que le graphique d'objet n'ait pas à être corrigé. J'espère que cela répondra également à votre question en supposant que j'ai besoin d'une instance de SomeViewModelde mon cscode, comment dois-je le faire?

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 */);
  }

}

Si votre application est de nature plus dynamique et est peut-être basée sur la navigation, vous devrez vous connecter au code qui effectue la navigation. Chaque fois que vous naviguez vers une nouvelle vue, vous devez créer un modèle de vue (à partir du conteneur DI), la vue elle-même et définir DataContextla vue sur le modèle de vue. Vous pouvez faire cette vue d'abord là où vous choisissez un modèle de vue basé sur une vue ou vous pouvez le faire d' abord modèle de vueoù le modèle de vue détermine la vue à utiliser. Un framework MVVM fournit cette fonctionnalité clé avec un moyen pour vous de connecter votre conteneur DI à la création de modèles de vue, mais vous pouvez également l'implémenter vous-même. Je suis un peu vague ici car en fonction de vos besoins cette fonctionnalité peut devenir assez complexe. C'est l'une des fonctions de base que vous obtenez d'un framework MVVM, mais déployer la vôtre dans une application simple vous donnera une bonne compréhension de ce que les frameworks MVVM fournissent sous le capot.

En ne pouvant pas déclarer le DataContexten XAML, vous perdez une partie de la prise en charge au moment du design. Si votre modèle de vue contient des données, il apparaîtra pendant la conception, ce qui peut être très utile. Heureusement, vous pouvez également utiliser des attributs au moment du design dans WPF. Une façon de procéder consiste à ajouter les attributs suivants à l' <Window>élément ou <UserControl>en 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}"

Le type de modèle de vue doit avoir deux constructeurs, celui par défaut pour les données au moment du design et un autre pour l'injection de dépendances:

class MyViewModel : INotifyPropertyChanged {

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

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

}

En faisant cela, vous pouvez utiliser l'injection de dépendances et conserver une bonne prise en charge au moment du design.

Martin Liversage
la source
13
Ceci est exactement ce que je cherchais. Cela me frustre le nombre de fois que je lis des réponses qui disent: «Utilisez simplement le framework [ yadde-ya ]». C'est bien beau, mais je veux savoir exactement comment lancer cela moi-même en premier , puis je pourrai savoir quel type de cadre pourrait réellement être utile pour moi. Merci de le préciser si clairement.
kmote
28

Ce que je poste ici est une amélioration de la réponse de sondergard, car ce que je vais dire ne rentre pas dans un commentaire :)

En fait, je présente une solution soignée, qui évite le besoin d'un ServiceLocator et d'un wrapper pour le StandardKernel-Instance, qui dans la solution de sondergard s'appelle IocContainer. Pourquoi? Comme mentionné, ce sont des anti-modèles.

Rendre le StandardKerneldisponible partout

La clé de la magie de Ninject est la StandardKernel-Instance qui est nécessaire pour utiliser la .Get<T>()-Méthode.

Alternativement à sondergard, IocContainervous pouvez créer l' StandardKernelintérieur de la App-Classe.

Supprimez simplement StartUpUri de votre 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>

Ceci est le CodeBehind de l'application dans 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ésormais, Ninject est vivant et prêt à se battre :)

Injecter votre DataContext

Comme Ninject est vivant, vous pouvez effectuer toutes sortes d'injections, par exemple l'injection de Property Setter ou l' injection de constructeur la plus courante .

Voici comment vous injectez votre ViewModel dans vos Window« sDataContext

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

Bien sûr, vous pouvez également Injecter un IViewModelsi vous faites les bonnes liaisons, mais cela ne fait pas partie de cette réponse.

Accéder directement au noyau

Si vous avez besoin d'appeler directement des méthodes sur le noyau (par exemple .Get<T>()-Method), vous pouvez laisser le noyau s'injecter lui-même.

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

Si vous avez besoin d'une instance locale du noyau, vous pouvez l'injecter en tant que propriété.

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

Bien que cela puisse être très utile, je ne vous recommanderais pas de le faire. Notez simplement que les objets injectés de cette manière ne seront pas disponibles dans le constructeur, car ils seront injectés plus tard.

Selon ce lien, vous devez utiliser l'extension d'usine au lieu d'injecter le IKernel(DI Container).

L'approche recommandée pour utiliser un conteneur DI dans un système logiciel est que la racine de composition de l'application soit le seul endroit où le conteneur est directement touché.

La manière dont Ninject.Extensions.Factory doit être utilisée peut également être indiquée en rouge ici .

LuckyLikey
la source
Belle approche. Jamais exploré Ninject à ce niveau, mais je peux voir que je rate quelque chose :)
chose
@son thx. À la fin de votre réponse, vous avez déclaré que si quelqu'un a un meilleur moyen, veuillez le partager. Pouvez-vous ajouter un lien ceci?
LuckyLikey
si quelqu'un est intéressé par la façon de l'utiliser Ninject.Extensions.Factory, indiquez-le ici dans les commentaires et j'ajouterai plus d'informations.
LuckyLikey
1
@LuckyLikey: Comment pourrais-je ajouter un ViewModel à un contexte de données de fenêtre via XAML qui n'a pas de constructeur sans paramètre? Avec la solution de sondergard avec le ServiceLocator, cette situation serait possible.
Thomas Geulen
Alors s'il vous plaît dites-moi comment récupérer les services dont j'ai besoin dans les propriétés attachées? Ils sont toujours statiques, à la fois le DependencyPropertychamp de sauvegarde et ses méthodes Get et Set.
élastique76
12

J'opte pour une approche "vue d'abord", où je passe le modèle de vue au constructeur de la vue (dans son code-behind), qui est assigné au contexte de données, par exemple

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

        DataContext = viewModel;
    }
}

Cela remplace votre approche basée sur XAML.

J'utilise le framework Prism pour gérer la navigation - quand un code demande qu'une vue particulière soit affichée (en "naviguant" dessus), Prism résoudra cette vue (en interne, en utilisant le framework DI de l'application); le framework DI résoudra à son tour toutes les dépendances de la vue (le modèle de vue dans mon exemple), puis résoudra ses dépendances, et ainsi de suite.

Le choix du framework DI est à peu près hors de propos car ils font tous essentiellement la même chose, c'est-à-dire que vous enregistrez une interface (ou un type) avec le type concret que vous voulez que le framework instancie quand il trouve une dépendance sur cette interface. Pour mémoire, j'utilise Castle Windsor.

La navigation Prism prend un certain temps pour s'y habituer, mais elle est plutôt bonne une fois que vous avez compris, vous permettant de composer votre application en utilisant différentes vues. Par exemple, vous pouvez créer une "région" Prism sur votre fenêtre principale, puis en utilisant la navigation Prism, vous passez d'une vue à une autre dans cette région, par exemple lorsque l'utilisateur sélectionne des éléments de menu ou autre.

Vous pouvez également jeter un œil à l'un des frameworks MVVM tels que MVVM Light. Je n'ai aucune expérience de ceux-ci, je ne peux donc pas commenter ce qu'ils aiment utiliser.

Andrew Stephens
la source
1
Comment passez-vous des arguments de constructeur aux vues enfants? J'ai essayé cette approche, mais j'obtiens des exceptions dans la vue parent qui me disent que la vue enfant n'a pas de constructeur sans paramètre par défaut
Doctor Jones
10

Installez MVVM Light.

Une partie de l'installation consiste à créer un localisateur de modèle de vue. Il s'agit d'une classe qui expose vos modèles de vue en tant que propriétés. Le getter de ces propriétés peut ensuite être des instances renvoyées à partir de votre moteur IOC. Heureusement, MVVM light inclut également le framework SimpleIOC, mais vous pouvez en connecter d'autres si vous le souhaitez.

Avec un simple IOC, vous enregistrez une implémentation par rapport à un type ...

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

Dans cet exemple, votre modèle de vue est créé et transmis à un objet de fournisseur de services selon son constructeur.

Vous créez ensuite une propriété qui renvoie une instance d'IOC.

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

La partie intelligente est que le localisateur de modèle de vue est ensuite créé dans app.xaml ou équivalent en tant que source de données.

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

Vous pouvez maintenant vous lier à sa propriété 'MyViewModel' pour obtenir votre viewmodel avec un service injecté.

J'espère que cela pourra aider. Toutes mes excuses pour les inexactitudes de code, codées à partir de la mémoire sur un iPad.

Kidshaw
la source
Vous ne devriez pas avoir un GetInstanceou en resolvedehors du bootstrap de l'application. C'est le but de DI!
Soleil - Mathieu Prévot
Je suis d'accord que vous pouvez définir la valeur de la propriété au démarrage, mais suggérer que l'utilisation de l'instanciation paresseuse est contre DI est une erreur.
kidshaw
@kishaw, je ne l'ai pas fait.
Soleil - Mathieu Prévot
3

Étui Canonic DryIoc

Répondre à un ancien message, mais faire cela avec DryIocet faire ce que je pense est une bonne utilisation de la DI et des interfaces (utilisation minimale de classes concrètes).

  1. Le point de départ d'une application WPF est App.xaml , et là, nous disons quelle est la vue initiale à utiliser; nous faisons cela avec du code derrière au lieu du xaml par défaut:
  2. retirer StartupUri="MainWindow.xaml" dans App.xaml
  3. dans codebehind (App.xaml.cs) ajoutez ceci override OnStartup:

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

c'est le point de départ; c'est aussi le seul endroit où resolveil faut appeler.

  1. la racine de configuration (selon le livre de Mark Seeman Dependency injection in .NET; le seul endroit où les classes concrètes devraient être mentionnées) sera dans le même code derrière, dans le constructeur:

    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>();
    }

Remarques et quelques détails supplémentaires

  • J'ai utilisé la classe concrète uniquement avec la vue MainWindow;
  • J'ai dû spécifier le constructeur à utiliser (nous devons le faire avec DryIoc) pour le ViewModel, car le constructeur par défaut doit exister pour le concepteur XAML, et le constructeur avec injection est celui qui est actuellement utilisé pour l'application.

Le constructeur ViewModel avec DI:

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

Constructeur par défaut de ViewModel pour la conception:

public MainWindowViewModel()
{
}

Le code derrière la vue:

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

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

et ce qui est nécessaire dans la vue (MainWindow.xaml) pour obtenir une instance de conception avec ViewModel:

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

Conclusion

Nous avons donc obtenu une implémentation très propre et minimale d'une application WPF avec un conteneur DryIoc et une DI tout en gardant les instances de conception de vues et de modèles de vue possibles.

Soleil - Mathieu Prévot
la source
2

Utilisez l' infrastructure d'extensibilité gérée .

[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);
}

En général, ce que vous feriez est d'avoir une classe statique et d'utiliser le modèle d'usine pour vous fournir un conteneur global (mis en cache, natch).

Quant à la façon d'injecter les modèles de vue, vous les injectez de la même manière que vous injectez tout le reste. Créez un constructeur d'importation (ou placez une instruction d'importation sur une propriété / un champ) dans le code-behind du fichier XAML et indiquez-lui d'importer le modèle de vue. Rabattre ensuite votre Windowest DataContextà cette propriété. Les objets racine que vous retirez vous-même du conteneur sont généralement des Windowobjets composés . Ajoutez simplement des interfaces aux classes de fenêtres et exportez-les, puis récupérez-les dans le catalogue comme ci-dessus (dans App.xaml.cs ... c'est le fichier de démarrage WPF).

Néologisme intelligent
la source
Il vous manque un point important de DI qui est d'éviter toute création d'instance avec new.
Soleil - Mathieu Prévot
0

Je suggérerais d'utiliser le ViewModel - Première approche https://github.com/Caliburn-Micro/Caliburn.Micro

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

utiliser Castle Windsorcomme conteneur IOC.

Tout sur les conventions

L'une des principales caractéristiques de Caliburn.Micro est manifeste dans sa capacité à supprimer le besoin de code de plaque de chaudière en agissant sur une série de conventions. Certaines personnes aiment les conventions et d'autres les détestent. C'est pourquoi les conventions de CM sont entièrement personnalisables et peuvent même être complètement désactivées si elles ne sont pas souhaitées. Si vous comptez utiliser des conventions, et comme elles sont activées par défaut, il est bon de savoir quelles sont ces conventions et comment elles fonctionnent. C'est le sujet de cet article. Résolution de la vue (ViewModel-First)

Basiques

La première convention que vous êtes susceptible de rencontrer lors de l'utilisation de CM est liée à la résolution de la vue. Cette convention affecte toutes les zones ViewModel-First de votre application. Dans ViewModel-First, nous avons un ViewModel existant que nous devons rendre à l'écran. Pour ce faire, CM utilise un modèle de dénomination simple pour trouver un UserControl1 qu'il doit lier au ViewModel et afficher. Alors, quel est ce modèle? Jetons un coup d'œil à ViewLocator.LocateForModelType pour découvrir:

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);
};

Ignorons d'abord la variable «context». Pour dériver la vue, nous partons du principe que vous utilisez le texte «ViewModel» dans la dénomination de vos VM, donc nous changeons simplement cela en «View» partout où nous le trouvons en supprimant le mot «Model». Cela a pour effet de changer les noms de type et les espaces de noms. Donc ViewModels.CustomerViewModel deviendrait Views.CustomerView. Ou si vous organisez votre application par fonctionnalité: CustomerManagement.CustomerViewModel devient CustomerManagement.CustomerView. J'espère que c'est assez simple. Une fois que nous avons le nom, nous recherchons les types avec ce nom. Nous recherchons tout assembly que vous avez exposé à CM comme pouvant être recherché via AssemblySource.Instance.2 Si nous trouvons le type, nous créons une instance (ou en obtenons une dans le conteneur IoC s'il est enregistré) et la renvoyons à l'appelant. Si nous ne trouvons pas le type,

Maintenant, revenons à cette valeur de «contexte». C'est ainsi que CM prend en charge plusieurs vues sur le même ViewModel. Si un contexte (généralement une chaîne ou une énumération) est fourni, nous effectuons une transformation supplémentaire du nom, en fonction de cette valeur. Cette transformation suppose en fait que vous avez un dossier (espace de noms) pour les différentes vues en supprimant le mot «Vue» à la fin et en ajoutant le contexte à la place. Ainsi, étant donné un contexte de «Master», nos ViewModels.CustomerViewModel deviendraient Views.Customer.Master.

Nahum
la source
2
Tout votre message est une opinion.
John Peters
-1

Supprimez l'URI de démarrage de votre 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);
    }
}

Vous pouvez maintenant utiliser votre classe IoC pour construire les instances.

MainWindowView.xaml.cs

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

        //Do other configuration            

        DataContext = mainWindowViewModel;

        InitializeComponent();
    }

}
C Bauer
la source
Vous ne devriez pas avoir de conteneur GetInstanced' resolveapp.xaml.cs extérieur, vous perdez le point de DI. De plus, mentionner la vue xaml dans le code de la vue est un peu compliqué. Appelez simplement la vue en c # pur et faites-le avec le conteneur.
Soleil - Mathieu Prévot