MVVM and IOC: Handling View Model's Class Invariants
This is an issue I've been struggling with since I started using MVVM, first in WPF and now in Silverlight.
I use an IOC container to manage the resolution of Views and ViewModels. Views tend to be very basic, with a default constructor, but ViewModels tend to access real services, all of which are required for their construction. Again, I use an IOC container for resolution, so injecting services is not a problem.
What does become a problem is passing required data to the ViewModel using IOC. As a simple example, consider a screen that allows the editing of a customer. In addition to any services it might require, the ViewModel for this screen requires a customer object for displaying/editing the customer's data.
When doing any type of (non-MVVM) library development, I consider it an unbendable rule that class invariants be passed via the constructor. In cases where I need context-specific data for class construction time and the class in question is conta开发者_如何学运维iner-managed, I tend to use an abstract factory* as a bridge. In MVVM this seems like overkill, as most ViewModels will then require their own factory.
A few other approaches I have tried/considered included (1) an initialize/load method in which I pass the data, which violates the rule of forcing class invariants through the constructor, (2) passing data through the container as parameter overrides (Unity), and (3) passing data through a global state bag (ugh).
What are some alternative ways to pass context-specific data from one ViewModel to the next? Do any of the MVVM frameworks address this specific problem?
*which can have its own problems, like requiring a choice between a call to Container.Resolve() or not having your ViewModel container-managed. Castle Windsor has a good solution to this, but AFAIK no other framework does.
Edit:
I forgot to add: some of the options I listed are not even possible if you are doing "View First" MVVM, unless you pass data first to the View and then to the ViewModel.
I'm not quite sure what the issue is, so I'll use a simple and contrived example.
Let's say you have a CustomerListViewModel
which lists a summary of each customer. When you select a customer, you want to display a CustomerDetailViewModel
. This could take either a customer ID, or an ICustomer
type which is populated previously in the CustomerListViewModel
with the customer details (depending on when you want to load the data for example).
I think what you're asking is what happens if CustomerDetailViewModel
also takes a series of services as dependencies which you want to resolve through the container (normally for dependency chains).
As you're doing view model first, you need to instantiate the CustomerDetailViewModel
from the CustomerListViewModel
, and you want to do so via the container so that the dependencies are injected appropriately.
Therefore, as you mention, you would normally do this through an abstract factory pattern, for example ICustomerDetailViewModelFactory
which gets passed as a service to the CustomerListViewModel
.
This factory type has a ICustomerDetailViewModel GetCustomerDetailViewModel(ICustomer customer)
method. This factory type will require a reference to your IoC container.
When resolving the ICustomerDetailViewModel
in your GetCustomerDetailViewModel
factory method, you can specify the value you wish to use for the ICustomer constructor parameter when you call Resolve on your container.
For example, Unity has parameter overrides, and see here for Castle Windsor support. Castle Windsor also has a typed factory facility, so that you don't need to implement the concrete factory types, just the abstractions.
So I would go with option 2! We use Caliburn.Micro, it solves a lot of MVVM problems, but I don't know of any frameworks which address this issue.
I used to struggle on this very issue a lot.
As far I can tell, there are no other viable approaches; you seem to have already deeply pondered the matter by yourself.
I just want two add my two 0.5 cents on the reasons why I quite often choose the option (1):
- the init method is simpler to implement than any other options (well, Windsor's Typed Factory are just as easy);
- the design weakness of not having a contructor parameter could be mitigated enforcing a check of the initialization parameters later in VM lifecycle
- the "place" where you would call the init method is the same where you would have called the constructor (or the abstract factory);
- unlike abstract factory, you can factor out the init method in a specific interface in order to handle several VM on different inheritance path (if neeeded);
- it's a fair compromise (at least in this context): if you really can't live with it, just go for the factory solution without caring about the (very little) complexity overhead.
I'm not so sure that MVVM and IoC lend themselves to having class invariants in constructors. In my experience, ViewModels are created as the result of an ICommand.Execute, which allows for a simple two stage process:
var vm = Container.Resolve<CustomerViewModel>();
vm.Model = CustomerRepository.GetCustomerModel(id);
At this stage, my View has no knowledge of the ViewModel, and that will only happen when I inject the ViewModel into whatever container is bound to the View. I'm also using DataTemplates to render the ViewModel which means I don't have to instantiate a View directly in order to supply a DataContext.
So, to answer, I'd use (1), and break the "rule".
精彩评论