How to handle dependency injection in a WPF/MVVM application
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.