4

Trying to backfill a WPF application using the MVVM pattern to work with dependency injection. I'm not overly familiar with DI, having worked with it only once before, but I think I understand the principles involved.

I need to ensure that the bindings are all registered in one place - the application root. In WPF, this is the OnStartup method. So, I grabbed Ninject and threw it into my application to try and automatically bind my repository class to the initial view:

private void OnStartup(object sender, StartupEventArgs e)
{
    IKernel kernel = new StandardKernel();
    kernel.Bind<IRepository>().To<Repository>();

    Views.MainView view = new Views.MainView();
    view.DataContext = kernel.Get<ViewModels.MainViewModel>();

    view.Show();
}

From hereon in, I set contexts by using a data template resource:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:views="clr-namespace:My.Views"
                    xmlns:models="clr-namespace:My.ViewModels" >
    <DataTemplate DataType="{x:Type models:MyViewModel}" >
        <views:MyView />
    </DataTemplate>
    <!-- etc -->
</ResourceDictionary>

And it works. Great! However, in MainViewModel I press a button and load a different type of ViewModel into the window:

NavigationHelper.NewWindow(this, new QuoteViewModel(quote, new Repository()));

This line of code is exactly what bought me to DI in the first place - I can't test this, because I can't mock out the dependency here. Adding DI in this instance isn't helping me at all because I'm only supposed to use my IoC container in OnStartUp, so I can't use kernel.Get to fetch my QuoteViewModel, right?

Snooping around SO I see a number of people recommending I solve this using a service locator. This is new to me, and I also see a number of people telling me that using this for DI is an anti-pattern which shouldn't be touched with a bargepole. Who's right?

And perhaps more importantly, is there a neat solution to this issue? I've seen a couple of other examples that demand a smorgasbord of different packages to make it work. Right now, it feels like MVVM and DI just aren't made to play nice with each other.

Bob Tway
  • 9,301
  • 17
  • 80
  • 162
  • 1
    You should definitely check out [this q/a](https://stackoverflow.com/questions/32307033/how-to-use-wpf-controls-with-simple-injector-dependencies) about doing DI in WPF/MVVM and [this one](https://stackoverflow.com/questions/28398363/change-current-implementation-of-basic-mvvm-to-adhere-to-solid-pattern/28465845#28465845) as well. – Steven Dec 09 '15 at 17:03

2 Answers2

3

You're almost there. You're missing two things:

A factory for the child view-model

You need a factory that is able to create the child VM for you. Introduce an interface for this factory, so you can replace it in tests.

public interface IVmFactory {
    IQuoteViewModel CreateQuoteViewModel();
}

You can implement this interface yourself or let NInject do it for you.

Be sure to register this factory in the DI container, so that the container is able to resolve it when it receives requests to instantiate classes that have the factory as dependency.

Proper DI in ViewModels.MainViewModel

Now, you can inject the IVmFactory into the view model with standard constructor injection:

public class MainViewModel {
    public MainViewModel(IVmFactory vmFactory) {
        _vmFactory = vmFactory;
    }

    // ...
}

DI and MVVM

DI and MVVM work together very well once you find your way around the default constructor problem of WPF (when you try to let WPF instantiate VMs).

DI is clearly not an anti-pattern, but service locator is. Unfortunately, service locator is often recommended together with MVVM, because it enables you to get started quickly without having to create factories and properly inject them. The trade-off is to get started quickly vs. having a clean & testable design - decide for yourself.

theDmi
  • 17,546
  • 6
  • 71
  • 138
-1

I agree that service locator is an anti-pattern, I personally solve this by creating a wrapper class for StandardKernel (Injector) which implements an interface (IInjector) which then injects itself into the classes that require it via field injection:

[Inject] public IInjector Injector {get; set;}

void MyClassMethod()
{
    var instance = this.Injector.Get<ISomeInterface>();
    // etc
}

This eliminates the service location anti-pattern and also abstracts away the implementation-specific details of the DI framework.

Mark Feldman
  • 15,731
  • 3
  • 31
  • 58
  • This is still a service locator in disguise. By having `IInjector` as a dependency, a class basically communicates "I depend on the whole world." This is hurts readability and testability, and violates the Interface Segregation Principle. – theDmi Dec 10 '15 at 13:15
  • True, but that's only in the specific case where all instances receive the same injector. – Mark Feldman Dec 11 '15 at 03:44
  • 1
    No, that's always the case: Type-wise, you can resolve any type through the injector. You could write `Injector.Get()`, and it would compile. This is bad because it doesn't help the developer, the interface can resolve anything (type system wise). You implemented the [Abstract Service Locator variation](http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/). – theDmi Dec 11 '15 at 07:10
  • ... and you don't see what dependencies a class really has, you just see that is uses the injector. – theDmi Dec 11 '15 at 07:13
  • Good point, guess I need to go brush up on my design patterns. – Mark Feldman Dec 11 '15 at 23:14