Je m'excuse pour la longue question, elle se lit un peu comme une diatribe, mais je promets que non! J'ai résumé ma ou mes questions ci-dessous
Dans le monde MVC, les choses sont simples. Le modèle a un état, la vue montre le modèle, et le contrôleur le fait des trucs vers/avec le modèle (en gros), un contrôleur n'a pas d'état. Pour faire des choses, le contrôleur a des dépendances sur les services Web, le référentiel, le lot. Lorsque vous instanciez un contrôleur, vous vous souciez de fournir ces dépendances, rien d'autre. Lorsque vous exécutez une action (méthode sur Controller), vous utilisez ces dépendances pour récupérer ou mettre à jour le modèle ou appeler un autre service de domaine. S'il y a un contexte, par exemple, comme un utilisateur souhaite voir les détails d'un élément particulier, vous passez l'ID de cet élément comme paramètre à l'action. Nulle part dans le contrôleur, il n'y a de référence à un état. Jusqu'ici tout va bien.
Entrez MVVM. J'adore WPF, j'adore la liaison de données. J'adore les frameworks qui rendent la liaison de données à ViewModels encore plus facile (en utilisant Caliburn Micro a.t.m.). Je pense cependant que les choses sont moins simples dans ce monde. Reprenons l'exercice: le modèle a un état, la vue montre le ViewModel, et le ViewModel le fait des trucs vers/avec le modèle (en gros), un ViewModel a un état! (pour clarifier; peut-être qu'il délègue toutes les propriétés à un ou plusieurs modèles, mais cela signifie qu'il doit avoir une référence au modèle d'une manière ou d'une autre, ce qui est un état en soi) Pour faire le ViewModel a des dépendances sur les services Web, le référentiel, le lot. Lorsque vous instanciez un ViewModel, vous vous souciez de fournir ces dépendances, mais aussi l'état. Et cela, mesdames et messieurs, m'agace sans fin.
Chaque fois que vous devez instancier un ProductDetailsViewModel
à partir du ProductSearchViewModel
(à partir duquel vous avez appelé le ProductSearchWebService
qui à son tour a renvoyé IEnumerable<ProductDTO>
, Tout le monde est toujours avec moi?), vous pouvez faire l'une de ces choses:
new ProductDetailsViewModel(productDTO, _shoppingCartWebService /* dependcy */);
, c'est mauvais, imaginez 3 autres dépendances, cela signifie que ProductSearchViewModel
doit également prendre en charge ces dépendances. Changer le constructeur est également douloureux._myInjectedProductDetailsViewModelFactory.Create().Initialize(productDTO);
, l'usine n'est qu'un Func, ils sont facilement générés par la plupart des frameworks IoC. Je pense que c'est mauvais parce que les méthodes Init sont une abstraction qui fuit. Vous ne pouvez pas non plus utiliser le mot clé readonly pour les champs définis dans la méthode Init. Je suis sûr qu'il y a quelques autres raisons._myInjectedProductDetailsViewModelAbstractFactory.Create(productDTO);
Alors ... c'est le modèle (fabrique abstraite) qui est généralement recommandé pour ce type de problème. Je pensais que c'était génial car il satisfait mon envie de taper statique, jusqu'à ce que je commence à l'utiliser. Je pense que la quantité de code passe-partout est excessive (vous savez, à part les noms de variables ridicules que j'utilise). Pour chaque ViewModel qui a besoin de paramètres d'exécution, vous obtiendrez deux fichiers supplémentaires (interface d'usine et implémentation), et vous devrez taper les dépendances non d'exécution comme 4 fois supplémentaires. Et chaque fois que les dépendances changent, vous pouvez également les changer en usine. J'ai l'impression que je n'utilise même plus de conteneur DI. (Je pense Castle Windsor a une sorte de solution pour cela [avec ses propres inconvénients, corrigez-moi si je me trompe]).Donc voilà. Mélanger l'état et le comportement de cette manière crée un problème qui n'existe pas du tout dans MVC. Et j'ai l'impression qu'il n'y a actuellement pas de solution vraiment adéquate à ce problème. J'aimerais maintenant observer certaines choses:
Pour résumer
Le problème des dépendances lors du lancement d'un nouveau modèle de vue peut être traité avec IOC.
public class MyCustomViewModel{
private readonly IShoppingCartWebService _cartService;
private readonly ITimeService _timeService;
public ProductDTO ProductDTO { get; set; }
public ProductDetailsViewModel(IShoppingCartWebService cartService, ITimeService timeService){
_cartService = cartService;
_timeService = timeService;
}
}
Lors de la mise en place du conteneur ...
Container.Register<IShoppingCartWebService,ShoppingCartWebSerivce>().As.Singleton();
Container.Register<ITimeService,TimeService>().As.Singleton();
Container.Register<ProductDetailsViewModel>();
Lorsque vous avez besoin de votre modèle de vue:
var viewmodel = Container.Resolve<ProductDetailsViewModel>();
viewmodel.ProductDTO = myProductDTO;
Lorsque vous utilisez un framework tel que caliburn micro il y a souvent une certaine forme de conteneur IOC déjà présent.
SomeCompositionView view = new SomeCompositionView();
ISomeCompositionViewModel viewModel = IoC.Get<ISomeCompositionViewModel>();
ViewModelBinder.Bind(viewModel, view, null);
Réponse courte à vos questions:
La version longue:
Nous sommes confrontés au même problème et avons trouvé certaines choses qui pourraient vous aider. Bien que je ne connaisse pas la solution "magique", ces choses soulagent un peu la douleur.
Implémentez des modèles pouvant être liés à partir de DTO pour le suivi et la validation des modifications. Ces "Data" -ViewModels ne doivent pas dépendre des services et ne proviennent pas du conteneur. Ils peuvent être simplement "nouveaux" édités, transmis et peuvent même dériver du DTO. L'essentiel est d'implémenter un modèle spécifique à votre application (comme MVC).
Découplez vos ViewModels. Caliburn facilite le couplage des ViewModels. Il le suggère même à travers son modèle Screen/Conductor. Mais ce couplage rend les ViewModels difficiles à tester, crée beaucoup de dépendances et le plus important: impose la charge de la gestion du cycle de vie de ViewModel à vos ViewModels. Une façon de les découpler est d'utiliser quelque chose comme un service de navigation ou un contrôleur ViewModel. Par exemple.
interface publique IShowViewModels {void Show (object inlineArgumentsAsAnonymousType, string regionId); }
Mieux encore, cela se fait par une forme de messagerie. Mais l'important n'est pas de gérer le cycle de vie de ViewModel à partir d'autres ViewModels. Dans MVC, les contrôleurs ne dépendent pas les uns des autres, et dans MVVM ViewModels ne doivent pas dépendre les uns des autres. Intégrez-les par d'autres moyens.
INeedData<T1,T2,...>
et en appliquant des paramètres de création de type sécurisé, cela n'en vaut pas la peine. La création d'usines pour chaque type de ViewModel n'en vaut pas la peine. La plupart des conteneurs IoC fournissent des solutions à cela. Vous obtiendrez des erreurs lors de l'exécution, mais le découplage et la testabilité de l'unité en valent la peine. Vous faites toujours une sorte de test d'intégration et ces erreurs sont facilement repérées.Je travaille quotidiennement avec ASP.NET MVC et travaille sur un WPF depuis plus d'un an et voici comment je le vois:
MVC
Le contrôleur est censé orchestrer des actions (récupérer ceci, ajouter cela).
La vue est responsable de l'affichage du modèle.
Le modèle englobe généralement les données (ex. UserId, FirstName) ainsi que l'état (ex. Titles) et est généralement spécifique à la vue.
MVVM
Le modèle ne contient généralement que des données (ex. UserId, FirstName) et est généralement transmis
Le modèle de vue englobe le comportement de la vue (méthodes), de ses données (modèle) et des interactions (commandes) - similaire au modèle MVP actif où le présentateur connaît le modèle. Le modèle de vue est spécifique à la vue (1 vue = 1 modèle de vue).
La vue est responsable de l'affichage des données et de la liaison de données au modèle de vue. Lorsqu'une vue est créée, son modèle de vue associé est généralement créé avec elle.
Ce que vous devez retenir, c'est que le modèle de présentation MVVM est spécifique à WPF/Silverlight en raison de leur nature de liaison de données.
La vue sait généralement à quel modèle de vue elle est associée (ou une abstraction de celle-ci).
Je vous conseille de traiter le modèle de vue comme un singleton, même s'il est instancié par vue. En d'autres termes, vous devriez pouvoir le créer via DI via un conteneur IOC et appeler des méthodes appropriées dessus pour dire; charger son modèle en fonction de paramètres. Quelque chose comme ceci:
public partial class EditUserView
{
public EditUserView(IContainer container, int userId) : this() {
var viewModel = container.Resolve<EditUserViewModel>();
viewModel.LoadModel(userId);
DataContext = viewModel;
}
}
Par exemple, dans ce cas, vous ne créeriez pas de modèle de vue spécifique à l'utilisateur mis à jour - à la place, le modèle contiendrait des données spécifiques à l'utilisateur qui seraient chargées via un appel sur le modèle de vue.
La façon dont je le fais habituellement (en utilisant PRISM), est que chaque assemblage contient un module d'initialisation de conteneur, où toutes les interfaces, les instances sont enregistrées au démarrage.
private void RegisterResources()
{
Container.RegisterType<IDataService, DataService>();
Container.RegisterType<IProductSearchViewModel, ProductSearchViewModel>();
Container.RegisterType<IProductDetailsViewModel, ProductDetailsViewModel>();
}
Et étant donné vos exemples de classes, serait implémenté comme ceci, avec le conteneur passé tout au long. De cette façon, toutes les nouvelles dépendances peuvent être ajoutées facilement car vous avez déjà accès au conteneur.
/// <summary>
/// IDataService Interface
/// </summary>
public interface IDataService
{
DataTable GetSomeData();
}
public class DataService : IDataService
{
public DataTable GetSomeData()
{
MessageBox.Show("This is a call to the GetSomeData() method.");
var someData = new DataTable("SomeData");
return someData;
}
}
public interface IProductSearchViewModel
{
}
public class ProductSearchViewModel : IProductSearchViewModel
{
private readonly IUnityContainer _container;
/// <summary>
/// This will get resolved if it's been added to the container.
/// Or alternately you could use constructor resolution.
/// </summary>
[Dependency]
public IDataService DataService { get; set; }
public ProductSearchViewModel(IUnityContainer container)
{
_container = container;
}
public void SearchAndDisplay()
{
DataTable results = DataService.GetSomeData();
var detailsViewModel = _container.Resolve<IProductDetailsViewModel>();
detailsViewModel.DisplaySomeDataInView(results);
// Create the view, usually resolve using region manager etc.
var detailsView = new DetailsView() { DataContext = detailsViewModel };
}
}
public interface IProductDetailsViewModel
{
void DisplaySomeDataInView(DataTable dataTable);
}
public class ProductDetailsViewModel : IProductDetailsViewModel
{
private readonly IUnityContainer _container;
public ProductDetailsViewModel(IUnityContainer container)
{
_container = container;
}
public void DisplaySomeDataInView(DataTable dataTable)
{
}
}
Il est assez courant d'avoir une classe ViewModelBase, dont tous vos modèles de vue sont dérivés, qui contient une référence au conteneur. Tant que vous prenez l'habitude de résoudre tous les modèles de vue au lieu de new()'ing
, cela devrait rendre la résolution des dépendances beaucoup plus simple.
Parfois, il est bon d'aller à la définition la plus simple plutôt qu'à un exemple complet: http://en.wikipedia.org/wiki/Model_View_ViewModel peut-être en lisant le ZK Java = l'exemple est plus éclairant que le C # one.
D'autres fois, écoutez votre instinct ...
Le fait d'avoir beaucoup de ViewModels avec à la fois un état/un comportement est-il une odeur de conception?
Vos modèles sont-ils des mappages objet par table? Peut-être qu'un ORM aiderait à mapper aux objets de domaine tout en gérant l'entreprise ou en mettant à jour plusieurs tables.