How to handle dependency injection in a WPF/MVVM application

103,390

Solution 1

I have been using Ninject, and found that it's a pleasure to work with. Everything is set up in code, the syntax is fairly straightforward and it has a good documentation (and plenty of answers on SO).

So basically it goes like this:

Create the view model, and take the IStorage interface as constructor parameter:

class UserControlViewModel
{
    public UserControlViewModel(IStorage storage)
    {

    }
}

Create a ViewModelLocator with a get property for the view model, which loads the view model from Ninject:

class ViewModelLocator
{
    public UserControlViewModel UserControlViewModel
    {
        get { return IocKernel.Get<UserControlViewModel>();} // Loading UserControlViewModel will automatically load the binding for IStorage
    }
}

Make the ViewModelLocator an application wide resource in App.xaml:

<Application ...>
    <Application.Resources>
        <local:ViewModelLocator x:Key="ViewModelLocator"/>
    </Application.Resources>
</Application>

Bind the DataContext of the UserControl to the corresponding property in the ViewModelLocator.

<UserControl ...
             DataContext="{Binding UserControlViewModel, Source={StaticResource ViewModelLocator}}">
    <Grid>
    </Grid>
</UserControl>

Create a class inheriting NinjectModule, which will set up the necessary bindings (IStorage and the viewmodel):

class IocConfiguration : NinjectModule
{
    public override void Load()
    {
        Bind<IStorage>().To<Storage>().InSingletonScope(); // Reuse same storage every time

        Bind<UserControlViewModel>().ToSelf().InTransientScope(); // Create new instance every time
    }
}

Initialize the IoC kernel on application startup with the necessary Ninject modules (the one above for now):

public partial class App : Application
{       
    protected override void OnStartup(StartupEventArgs e)
    {
        IocKernel.Initialize(new IocConfiguration());

        base.OnStartup(e);
    }
}

I have used a static IocKernel class to hold the application wide instance of the IoC kernel, so I can easily access it when needed:

public static class IocKernel
{
    private static StandardKernel _kernel;

    public static T Get<T>()
    {
        return _kernel.Get<T>();
    }

    public static void Initialize(params INinjectModule[] modules)
    {
        if (_kernel == null)
        {
            _kernel = new StandardKernel(modules);
        }
    }
}

This solution does make use of a static ServiceLocator (the IocKernel), which is generally regarded as an anti-pattern, because it hides the class' dependencies. However it is very difficult to avoid some sort of manual service lookup for UI classes, since they must have a parameterless constructor, and you cannot control the instantiation anyway, so you cannot inject the VM. At least this way allows you to test the VM in isolation, which is where all the business logic is.

If anyone has a better way, please do share.

EDIT: Lucky Likey provided an answer to get rid of the static service locator, by letting Ninject instantiate UI classes. The details of the answer can be seen here

Solution 2

In your question you set the value of the DataContext property of the view in XAML. This requires that your view-model has a default constructor. However, as you have noted, this does not work well with dependency injection where you want to inject dependencies in the constructor.

So you cannot set the DataContext property in XAML and also do dependency injection. Instead you have other alternatives.

If you application is based on a simple hierarchical view-model you can construct the entire view-model hierarchy when the application starts (you will have to remove the StartupUri property from the App.xaml file):

public partial class App {

  protected override void OnStartup(StartupEventArgs e) {
    base.OnStartup(e);
    var container = CreateContainer();
    var viewModel = container.Resolve<RootViewModel>();
    var window = new MainWindow { DataContext = viewModel };
    window.Show();
  }

}

This is based around an object graph of view-models rooted at the RootViewModel but you can inject some view-model factories into parent view-models allowing them to create new child view-models so the object graph does not have to be fixed. This also hopefully answers your question suppose I need an instance of SomeViewModel from my cs code, how should I do it?

class ParentViewModel {

  public ParentViewModel(ChildViewModelFactory childViewModelFactory) {
    _childViewModelFactory = childViewModelFactory;
  }

  public void AddChild() {
    Children.Add(_childViewModelFactory.Create());
  }

  ObservableCollection<ChildViewModel> Children { get; private set; }

 }

class ChildViewModelFactory {

  public ChildViewModelFactory(/* ChildViewModel dependencies */) {
    // Store dependencies.
  }

  public ChildViewModel Create() {
    return new ChildViewModel(/* Use stored dependencies */);
  }

}

If your application is more dynamic in nature and perhaps is based around navigation you will have to hook into the code that performs the navigation. Each time you navigate to a new view you need to create a view-model (from the DI container), the view itself and set the DataContext of the view to the view-model. You can do this view first where you pick a view-model based on a view or you can do it view-model first where the view-model determines which view to use. A MVVM framework provides this key functionality with some way for you to hook your DI container into the creation of view-models but you can also implement it yourself. I am a bit vague here because depending on your needs this functionality may become quite complex. This is one of the core functions you get from a MVVM framework but rolling your own in a simple application will give you a good understanding what MVVM frameworks provide under the hood.

By not being able to declare the DataContext in XAML you lose some design-time support. If your view-model contains some data it will appear during design-time which can be very useful. Fortunately, you can use design-time attributes also in WPF. One way to do this is to add the following attributes to the <Window> element or <UserControl> in XAML:

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:MyViewModel, IsDesignTimeCreatable=True}"

The view-model type should have two constructors, the default for design-time data and another for dependency injection:

class MyViewModel : INotifyPropertyChanged {

  public MyViewModel() {
    // Create some design-time data.
  }

  public MyViewModel(/* Dependencies */) {
    // Store dependencies.
  }

}

By doing this you can use dependency injection and retain good design-time support.

Solution 3

What I'm posting here is an improvement to sondergard's Answer, because what I'm going to tell doesn't fit into a Comment :)

In Fact I am introducing a neat solution, which avoids the need of a ServiceLocator and a wrapper for the StandardKernel-Instance, which in sondergard's Solution is called IocContainer. Why? As mentioned, those are anti-patterns.

Making the StandardKernel available everywhere

The Key to Ninject's magic is the StandardKernel-Instance which is needed to use the .Get<T>()-Method.

Alternatively to sondergard's IocContainer you can create the StandardKernel inside the App-Class.

Just remove StartUpUri from your App.xaml

<Application x:Class="Namespace.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
             ... 
</Application>

This is the App's CodeBehind inside App.xaml.cs

public partial class App
{
    private IKernel _iocKernel;

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        _iocKernel = new StandardKernel();
        _iocKernel.Load(new YourModule());

        Current.MainWindow = _iocKernel.Get<MainWindow>();
        Current.MainWindow.Show();
    }
}

From now on, Ninject is alive and ready to fight :)

Injecting your DataContext

As Ninject is alive, you can perform all kinds of injections, e.g Property Setter Injection or the most common one Constructor Injection.

This is how you inject your ViewModel into your Window's DataContext

public partial class MainWindow : Window
{
    public MainWindow(MainWindowViewModel vm)
    {
        DataContext = vm;
        InitializeComponent();
    }
}

Of course you can also Inject an IViewModel if you do the right bindings, but that is not a part of this answer.

Accessing the Kernel directly

If you need to call Methods on the Kernel directly (e.g. .Get<T>()-Method), you can let the Kernel inject itself.

    private void DoStuffWithKernel(IKernel kernel)
    {
        kernel.Get<Something>();
        kernel.Whatever();
    }

If you would need a local instance of the Kernel you could inject it as Property.

    [Inject]
    public IKernel Kernel { private get; set; }

Allthough this can be pretty useful, I would not recommend you to do so. Just note that objects injected this way, will not be available inside the Constructor, because it's injected later.

According to this link you should use the factory-Extension instead of injecting the IKernel (DI Container).

The recommended approach to employing a DI container in a software system is that the Composition Root of the application be the single place where the container is touched directly.

How the Ninject.Extensions.Factory is to be used can also be red here.

Solution 4

I go for a "view first" approach, where I pass the view-model to the view's constructor (in its code-behind), which gets assigned to the data context, e.g.

public class SomeView
{
    public SomeView(SomeViewModel viewModel)
    {
        InitializeComponent();

        DataContext = viewModel;
    }
}

This replaces your XAML-based approach.

I use the Prism framework to handle navigation - when some code requests a particular view be displayed (by "navigating" to it), Prism will resolve that view (internally, using the app's DI framework); the DI framework will in turn resolve any dependencies that the view has (the view model in my example), then resolves its dependencies, and so on.

Choice of DI framework is pretty much irrelevant as they all do essentially the same thing, i.e. you register an interface (or a type) along with the concrete type that you want the framework to instantiate when it finds a dependency on that interface. For the record I use Castle Windsor.

Prism navigation takes some getting used to but is pretty good once you get your head around it, allowing you to compose your application using different views. E.g. you might create a Prism "region" on your main window, then using Prism navigation you would switch from one view to another within this region, e.g. as the user selects menu items or whatever.

Alternatively take a look at one of the MVVM frameworks such as MVVM Light. I've got no experience of these so can't comment on what they're like to use.

Solution 5

Install MVVM Light.

Part of the installation is to create a view model locator. This is a class which exposes your viewmodels as properties. The getter of these properties can then be returned instances from your IOC engine. Fortunately, MVVM light also includes the SimpleIOC framework, but you can wire in others if you like.

With simple IOC you register an implementation against a type...

SimpleIOC.Default.Register<MyViewModel>(()=> new MyViewModel(new ServiceProvider()), true);

In this example, your view model is created and passed a service provider object as per its constructor.

You then create a property which returns an instance from IOC.

public MyViewModel
{
    get { return SimpleIOC.Default.GetInstance<MyViewModel>; }
}

The clever part is that the view model locator is then created in app.xaml or equivalent as a data source.

<local:ViewModelLocator x:key="Vml" />

You can now bind to its 'MyViewModel' property to get your viewmodel with an injected service.

Hope that helps. Apologies for any code inaccuracies, coded from memory on an iPad.

Share:
103,390
Fedaykin
Author by

Fedaykin

Enthusiastic programmer, learning better practices every day and sharing as much as possible.

Updated on July 08, 2022

Comments

  • Fedaykin
    Fedaykin almost 2 years

    I am starting a new desktop application and I want to build it using MVVM and WPF.

    I am also intending to use TDD.

    The problem is that I don´t know how I should use an IoC container to inject my dependencies on my production code.

    Suppose I have the folowing class and interface:

    public interface IStorage
    {
        bool SaveFile(string content);
    }
    
    public class Storage : IStorage
    {
        public bool SaveFile(string content){
            // Saves the file using StreamWriter
        }
    }
    

    And then I have another class that has IStorage as a dependency, suppose also that this class is a ViewModel or a business class...

    public class SomeViewModel
    {
        private IStorage _storage;
    
        public SomeViewModel(IStorage storage){
            _storage = storage;
        }
    }
    

    With this I can easily write unit tests to ensure that they are working properly, using mocks and etc.

    The problem is when it comes to use it in the real application. I know that I must have an IoC container that links a default implementation for the IStorage interface, but how would I do that?

    For example, how would it be if I had the following xaml:

    <Window 
        ... xmlns definitions ...
    >
       <Window.DataContext>
            <local:SomeViewModel />
       </Window.DataContext>
    </Window>
    

    How can I correctly 'tell' WPF to inject dependencies in that case?

    Also, suppose I need an instance of SomeViewModel from my C# code, how should I do it?

    I feel I am completely lost in this, I would appreciate any example or guidance of how is the best way to handle it.

    I am familiar with StructureMap, but I am not an expert. Also, if there is a better/easier/out-of-the-box framework, please let me know.

  • JWP
    JWP about 9 years
    Your entire post is opinion.
  • user3141326
    user3141326 over 8 years
    I am new to dependency injection, yet in its heart your solution is combining the Service Locator anti-pattern with the Ninject since you are using the static ViewModel Locator. One could argue injection is done in Xaml file, which is less likely to be tested. I do not have a better solution and will probably use yours - yet I think it would be helpful to mention this in the answer as well.
  • LuckyLikey
    LuckyLikey about 7 years
    Man your solution is just great, there is only one "Problem" with the following Line: DataContext="{Binding [...]}". This is causing the VS-Designer to execute all Program-Code in the ViewModel's Constructor. In my case the Window is beeing executed and modally blocks any interaction to VS. Perhaps one should modify the ViewModelLocator not to locate the "real" ViewModels in Design-Time. - Another Solution is to "Disable Project Code", which will also prevent everything else from beeing shown. Maybe you've already found a neat solution to this. In this case I'd please you to show it.
  • sondergard
    sondergard about 7 years
    @LuckyLikey You can try to use d:DataContext="{d:DesignInstance vm:UserControlViewModel, IsDesignTimeCreatable=True}" but I'm not sure it makes a difference. But why/how is the VM constructor launching a modal window? And what kind of window?
  • LuckyLikey
    LuckyLikey about 7 years
    @son Actually I don't know why and how, but when i open a Window Designer from the Solution Explorer, as the new Tab is beeing opened, the window is beeing displayed by the designer and the same window appears as if debugging modal, hosted in a new process outside of VS "Micorosoft Visual Studio XAML Designer". If the process is shut down, also the VS-Designer fails with the preceeding mentioned exception. Im going to try your workaround. I'll notify you as I detect new Info :)
  • sondergard
    sondergard about 7 years
    @LuckyLikey Never experienced that. You could also bypass binding the ViewModelLocator binding, and create the VM in the code-behind directly. This way you can check if design mode first. So in the code-behind constructor: if (DesignerProperties.GetIsInDesignMode(this) == false) IocKernel.Get<UserControlViewModel>();
  • LuckyLikey
    LuckyLikey about 7 years
    @son Man I also tried this solution :DataContext="{d:DesignInstance vm:UserControlViewModel, IsDesignTimeCreatable=True}" It does help, but only if there's no parameterless constructior on ViewModel, because then no instance is createable from the designer. I guess if (DesignerProperties.GetIsInDesignMode(this) == false) is the only possibility to solve this.
  • LuckyLikey
    LuckyLikey about 7 years
    @sondergard I have posted an improvement to your answer, avoiding the ServiceLocator Anti-Pattern. Feel free to check it out.
  • sondergard
    sondergard about 7 years
    Nice approach. Never explored Ninject to this level, but I can see that I am missing out :)
  • LuckyLikey
    LuckyLikey almost 7 years
    if someone is interested on how to use Ninject.Extensions.Factory on this, state it here in the comments and I will add some more information.
  • kmote
    kmote almost 7 years
    This is EXACTLY what I was looking for. It frustrates me how many times I read answers that say "Just use the [yadde-ya] framework." That's all well and good, but I want to know exactly how to roll this myself first and then I can know what kind of framework might actually be of use for me. Thanks for spelling it out so clearly.
  • Thomas Geulen
    Thomas Geulen over 6 years
    @LuckyLikey: How would I be able to add a ViewModel to a window datacontext via XAML which has no parameterless constructor? With the solution from sondergard with the ServiceLocator this situation would be possible.
  • springy76
    springy76 over 6 years
    So please tell me how to retrieve services which I need in attached properties? They are always static, both the backing DependencyProperty field and its Get and Set methods.
  • Doctor Jones
    Doctor Jones about 5 years
    How do you pass constructor arguments to child views? I've tried this approach, but get exceptions in the parent view telling me that the child view does not have a default parameter-less constructor
  • Soleil
    Soleil about 5 years
    You should not have any container GetInstance of resolve outside app.xaml.cs, you're losing the point of DI. Also, mentionning the xaml view in the view's codebehind is kind of convoluted. Just call the view in pure c#, and do this with the container.
  • Soleil
    Soleil about 5 years
    You're missing one important point of DI which is to avoid any instance creation with new.
  • Soleil
    Soleil about 5 years
    You should not have a GetInstance or resolve outside the bootstrap of the application. That's the point of DI !
  • kidshaw
    kidshaw about 5 years
    I agree that you could set the property value during startup, but to suggest that using lazy instantiation is against DI is wrong.
  • Soleil
    Soleil about 5 years
    @kishaw I did not.
  • LuckyLikey
    LuckyLikey about 4 years
    @springy76 An interresting question. I know, I'm a little late, but what did you end up doing? As for the most part, I've never been using DI inside my attached-properties. Propably thats where you couldn't avoid a servicelocator?
  • Michael Schönbauer
    Michael Schönbauer about 3 years
    I agree with @user3141326 . this is not dependency injection, basically the di layer should be outside of the presentation layer, the application needs to have control over which viewmodellocator it wants to use. so it has to be in the app.xaml. luckylikers answer should be marked as the correct answer (below)
  • Shenron
    Shenron over 2 years
    "you cannot set the DataContext property in XAML" this is totally wrong
  • Shenron
    Shenron over 2 years
    Making the StandardKernel available everywhere is a very bad practice + I totally dislike injecting the viewmodel in the window constructor
  • Martin Liversage
    Martin Liversage over 2 years
    @Shenron Please read the preceding paragraph: This requires that your view-model has a default constructor. To avoid further confusion I have made a small edit to clarify that it's the combination of setting the data context in XAML and dependency injection that doesn't work.
  • LuckyLikey
    LuckyLikey over 2 years
    @Shenron totally agree, thats why I wrote about the ninject factory extension. anyway, these days I wouln't suggest to use ninject anymore, since there are much better frameworks around now.
  • Shenron
    Shenron over 2 years
    @MartinLiversage I still disagree, dependency injection does not prevent setting the datacontext in xaml. see stackoverflow.com/questions/54877352/…
  • Martin Liversage
    Martin Liversage over 2 years
    @Shenron My point is that you cannot set the data context in XAML "the generic WPF way" if the view model doesn't have a default constructor. I get that you don't like my choice of words but I still think it's a valid point. The question is about how to deal with that problem. There are multiple answers here and your linked answer provides an alternative solution by using a custom markup extension. You still had to come up with a solution to the problem and that's exactly what Stack Overflow is about: providing solutions to problems.
  • Melissa
    Melissa over 2 years
    A bit late to the party, but @LuckyLikey, what other frameworks would you suggest? I was convinced that Ninject was the one, as my requirement is to manually inject the services I need, but I shouldn't inject them in the constructor (as they may or may not be used).
  • Kappacake
    Kappacake over 2 years
    Great answer. +1 for the point regarding the design-time viewmodel - very useful! Can you also add a point regarding changing 'StartupUri' in App.xaml?