4

I have a simple WPF application which utilizes the Unity Framework for Dependency Injection. Currently, I am trying to simplify my method for navigation between views in my MVVM pattern implementation; however, many of the examples throughout Stack Overflow do not take into consideration the Dependency Injection caveat.

I have two entirely separate views.

One, Main acts as the main window into which content is loaded (pretty typical; unnecessary content eliminated):

<Window x:Class="Application.UI.Main">
    <Grid Background="White">
        <ContentControl Content="{Binding aProperty}"/>
    </Grid>
</Window>

The constructor receives a ViewModel via constructor injection (again, very simple):

    public partial class Main
    {
        private MainViewModel _mainViewModel;

        public Main (MainViewModel mainViewModel)
        {
            InitializeComponent();

           this.DataContext = _mainViewModel = mainViewModel;
        }
    }

I then have a UserControl, Home, to which I want to "navigate" the main window (i.e. set the ContentControl. It's constructor also receives a ViewModel via constructor injection in the same way Main does. It is equally simple:

public Home(HomeViewModel homeViewModel)
{
    InitializeComponent();

    // Set Data Context:
    this.DataContext = homeViewModel;
}

The main problem, here, is that I want to enable constructor-based injection while maintaining as pure an MVVM implementation as possible.

I am in the View-first camp of MVVM, about which you can find a good discussion in these comments.

I have seen some allusions to the idea of a navigation based service; however, am unsure if that maintains the separation of concerns strived for by MVVM. DataTemplates require View constructors that do not take arguments and I have read criticisms of DataTemplates that argue ViewModels should not participate in the instantiation of Views.

This solution (in my opinion) is just straight wrong as the ViewModel becomes aware of its View and relies on a service for ViewModel instantiaion which makes real Dependency Injection to resolve ViewModel and View dependencies all but impossible. This issue is very evident in the use of RelayCommand in this MSDN article.

Does a navigation service which maintains a global, singleton-like reference to the Main view make the most sense? Is it acceptable for the Main view to expose a method, e.g.:

public void SetContent(UserControl userControl) { //... }

That is then accessed by that service?

Community
  • 1
  • 1
Thomas
  • 6,291
  • 6
  • 40
  • 69
  • While you say you are in the 'View-first camp', the exact scenario you are trying to work through is easily and simply handled in the VM-first approach. Use your DI container to build your view-model graph, and let DataTemplates deal with the wiring. – Andrew Hanlon Oct 22 '15 at 16:56
  • @AndrewHanlon That was a change I just made a second ago (I am still trying to decide what each means - and I think I am actually in the VM-first camp). Can you direct me to an example? – Thomas Oct 22 '15 at 17:01
  • Oh, wait. In this case, the View wouldn't even be aware of the ViewModel except for the DataTemplate, correct? How then does `Main` know which View to display if I am using DI to resolve the dependencies? i.e. The dependencies must be there. With a framework like Unity I can't grab stuff from the IoC (I don't want to, either) – Thomas Oct 22 '15 at 17:03
  • 1
    So, in VM-first, the View is aware of the VM (or a VM contract interface) but has no direct injection of the VM, only through the DataContext, which is set automatically via the ContentControl. – Andrew Hanlon Oct 22 '15 at 17:07
  • @AndrewHanlon How then does `Main` know which View to display if I am using DI to resolve the dependencies? i.e. The dependencies must be there. With a framework like Unity I can't grab stuff from the IoC (I don't want to, either, as making direct calls to an IoC container isn't really DI). Or should I really just use a "smarter" IoC than Unity? – Thomas Oct 22 '15 at 17:08
  • 1
    Look at approach 7/8 from [Here](http://paulstovell.com/blog/mvvm-instantiation-approaches).As for initialization, just change the app.xml so that you Main view is not created automatically. Then you can do all of your VM initialization (DI) and then create a Main and set its DataContext. After this let DataTemplates do the wiring for you. – Andrew Hanlon Oct 22 '15 at 17:12
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/93091/discussion-between-thomas-and-andrew-hanlon). – Thomas Oct 22 '15 at 17:14

1 Answers1

2

This is my articulation of the motivations behind my implementation of the solution provided by another author. I do not provide code as great code examples are provided by the linked article. These are, of course, my opinions but also represent an amalgamation of my research into the topic

No-Container Needed Solution

Rachel Lim wrote a great article, Navigation with MVVM, which describes how to take full advantage of WPF's DataTemplates to solve the challenges presented by MVVM navigation. Lim's approach provides the "best" solution as it greatly reduces the need for any Framework dependencies; however, there really is no "great" way to solve the problem.

The greatest objection to Rachel's approach in general is that the View Model then becomes responsible for - or, "defines" - it's own relationship to the View. For Lim's solution, this is a minor objection for two reasons (and does nothing to further justify other bad architectural decisions, described later):

1.) The DataTemplate relationship is not enforced by anything other than an XAML file, i.e. the View Models are, themselves, never directly aware of their Views nor vice versa, so, even our View's constructor is further simplified, take for example the Home class constructor - now without need for a reference to a View Model:

public Home()
{
    InitializeComponent();
}

2.) As the relationship is expressed nowhere else, the association between a particular View and View Model is easy to change.

An application should be able to function (model the domain) sufficiently without a prescribed View. This ideal stems from the effort to best decouple the application's supporting architecture and to foster further application of SOLID programming principles, most specifically dependency injection.

The XAML file - not a third-party dependency container - becomes the pivotal point for resolution of the relationship between the View and the View Model (which, consequently, directly contradicts the OP).

Dependency Injection

An application should be designed to be entirely agnostic of its container and - even better - any implementation-specific information regarding service dependencies. What this allows us to do is to "assign" (inject) services that oblige by a certain contract (interface) to various classes that make up the meat of our applications' functionality.

This leaves us with two criteria for "good design":

  1. Application classes should be able to support a variety of services that perform the same task, as long as they oblige by a described contract.
  2. Application classes should never be aware of - or reference - their container, even if the container is Unity, PRISM, or Galasoft.

The second point is of utmost importance and is a "rule" that is broken, commonly. That "rule breaking" being the inspiration behind the original post.

The response to the navigation problem in many applications is to inject a wrapped dependency injection container which is then used to make calls out of the implementing class to resolve dependencies. The class now knows about the container and, worse, has even greater, more concrete knowledge of what specifics it needs in order to perform its operations (and some might argue more difficult to maintain).

Any knowledge of a dependency resolution container on the part of the View, View Model, or Model is an anti-pattern (you can read more about the justification for that statement elsewhere).

A well written application that relies on dependency injection could function without a dependency injection Framework, i.e. you could resolve the dependencies, manually, from handwritten bootstrapper (though that would require a great deal of careful work).

Lim's solution enables us to "not need" a reference to a container from within the implementation.

What we should be left with are constructors that look like:

// View:
public Home() { //... } 

// View Model 
public HomeViewModel (SomeModelClass someModel, MaybeAService someService)

If one goal is modularization and reusability, then the above achieves that, well. We could continue to abstract this out further by ensuring those passed-in dependencies are contract fulfillments via interfaces.

Thomas
  • 6,291
  • 6
  • 40
  • 69
  • hello Thomas. Your topic and your discussion with @Andrew Hanlon helps me a lot. Can you provide "Lim's approach" (link, code etc) for further reading? – dios231 Aug 18 '16 at 07:26
  • @dios231 the _Navigation with MVVM_ should be a hyperlink. Try clicking on it. If that doesn't work, let me know. I will try and flesh out the above, if not. – Thomas Aug 18 '16 at 17:51
  • hello again Thomas and thank you for your answer at an so old post. One question. At Rachel article at `ApplicationViewModel` constructor there is a list that contains all application ViewModels. Do you think this is a bad practice? How can you use DI since there is not composition root part at application bootstrap? – dios231 Aug 18 '16 at 18:49
  • @dios231 I was never really sure how I felt about the list of instantiated ViewModels. I ultimately had two concerns going with that approach: the size of list in memory if I have a large application and maintaining the list to accurately reflect the state of the application, e.g. the lifetime of a ViewModel. I am not sure I understand your question about DI. – Thomas Aug 18 '16 at 19:59
  • I have the same concerns as you. And the problem is that this list it seems to be a key part at Lim's solution. Also a ViewModel factory seems that is not the solution since it has to be aware about ViewModels dependencies which is pretty awkward. When I come up with a solution I will inform you. Thanks!!! – dios231 Aug 19 '16 at 06:42