views:

61

answers:

3

Should I register ViewModels in Container and resolve from there?

Benefits:

  1. I can perform some actions when view model is activated
  2. Container will inject dependencies for me
  3. ???

Drawbacks:

  1. ViewModel lifetime management can be tricky:
    • if I make ViewModel singleton then I can't instantiate several controls of the same type
    • if I make ViewModel transient then I can easily end up in a situation of having several different instances when I actually expect same instance injected
  2. ???

What's the right answer? I'd prefer to register if I could mitigate lifetime drawback.

I'm using Caliburn and Autofac if it matters.

+2  A: 

The benefits of 2) is enough for me to let the container handle viewmodels. We're using our own MVVM framework where there is a strict one-to-one relationship between view instances and viewmodel instances. Thus, the 1) drawbacks are non-existent.

In cases where we need to share data between views, we accomplish this by injecting the viewmodels with a shared service instance.

Except from that, are there other circumstances where you expect one viewmodel instance to be shared among several views?

Peter Lillevold
Say we have a window with date picker and several tabs. Each tab contains report, all reports should use same date from date picker. How would you implement this requirement?
Konstantin Spirin
@Konstantin - I would consider such a view as several views (with corresponding viewmodels). One view/model representing the whole window handling the selected date, and then one view/model for each tab. Using databinding you can then easily bind the SelectedDate in the main view to a property on each sub-view.
Peter Lillevold
A: 

Not sure about autofac or Caliburn (might still apply) but when it comers to the Unity Container I will only register the ViewModel if...

I need to have it disposed of when the container is disposed of. You can create a lifetime manager that will store the newly created (non singleton) view model instance.

container.RegisterType<MyViewModel>(new DisposeableInstanceLifetimeManager());
...
container.Resolve<MyViewModel>();  // here all dependencies will get injected
...
container.Dispose(); 

If you want to share data I tend to create a sub (child) container and register the model as a singleton and have multiple view models share the same model.

var child = container.CreateChildContainer();
child.RegisterInstance(model, new ContainerControlledLifetimeManager());
child.Resolve<MyViewModel1>();
child.Resolve<MyViewModel2>(); // both can share the model instance

(Note: with Unity all dependencies are injected when using Resolve on the ViewModel even if it is not registered with the container).

Otherwise unless you need a singleton ViewModel (although I cannot think of when that would be useful) I feel adding the ViewModel to the container just adds more code for no benefit.

aqwert
Main window of the application is a good candidate to be singleton. Main window often contains elements that are shared in the whole application. For example, status bar.With Unity I was doing very similar thing but it's easy to get lost because you need to think which ViewModel to register and which not to. Also it's quite scary to have ViewModel as a dependency since Unity can create new instance even if you haven't registered it.
Konstantin Spirin
+1  A: 

A container is an ecosystem inhabited by the objects it creates. View models interact with those inhabitants and thus are also part of the ecosystem. To accurately reflect that relationship, you should register view models in the container.

You should always use InstancePerDependency with view models. A view model represents the state and behavior of a particular piece of UI - it is the non-framework-specific analogue of a control. Just as you can't generally place the same control instance in two locations in a UI tree, you can't also reuse the same view model instance.

If you could, we'd call it a ViewsModel :-)

Bryan Watts
What about situations when one ViewModel is required to be injected in several other view models? Simplest example - Status Bar. Say we have several controls on the window (backed by several view models) and each of them from time to time updates message in the status bar.
Konstantin Spirin
Regarding using ViewModel to back multiple controls at once, consider grid and when you select a row you want to see details. Current row and details control will most probably be bound to the same view model, just showing them differently.
Konstantin Spirin
I like your first paragraph though!
Konstantin Spirin
@Konstantin Spirin: for the status bar, you would have a shared service that implements the communication between the view models, rather share a view model directly (@Peter Lillevold also suggested this). For the grid/row example, I almost always end up with a view model for the row, and a *separate* view model for the details dialog. They may both use the same entity instance, but will have different profiles and intents, such as an Edit command on the row and Save/Cancel commands on the dialog.
Bryan Watts
Let's consider status bar example again. `StatusBarService` will need to have `StatusBarViewModel` injected in order to function. The same `StatusBarViewModel` will have to be injected into `MainWindowViewModel` in order to be displayed. `InstancePerDependency` will give us two different instances of `StatusBarViewModel` so message will not be visible.
Konstantin Spirin
@Konstantin Spirin: I understand your example. My response to it, again, is that you would not inject `StatusBarViewModel` into `MainWindowViewModel`. Instead, you would inject something like `IStatusContext` into *both* `StatusBarViewModel` and `MainWindowViewModel`. If you register `IStatusContext` with `InstancePerLifetimeScope`, you get the desired outcome without compromising the view model/view cardinality.
Bryan Watts
@Konstantin Spirin: I just realized that you suggested injecting `StatusBarViewModel` *into* `StatusBarService`. I am suggesting the exact opposite: `IStatusContext` would be the point of communication between two view models, but it does *not* reference a view model itself. Thus, `StatusBarViewModel` only needs to be resolved once, avoiding those issues.
Bryan Watts
@Bryan Watts: Thanks!
Konstantin Spirin