问题
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.
回答1:
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.
回答2:
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.
来源:https://stackoverflow.com/questions/34184434/adding-dependency-injection-to-an-mvvm-application