问题描述
我正在启动一个新的桌面应用程序,我想使用 MVVM 和 WPF 构建它.
I am starting a new desktop application and I want to build it using MVVM and WPF.
我也打算使用 TDD.
I am also intending to use TDD.
问题是我不知道应该如何使用 IoC 容器将我的依赖项注入到我的生产代码中.
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
}
}
然后我有另一个类具有 IStorage
作为依赖项,还假设这个类是 ViewModel 或业务类...
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.
问题在于在实际应用程序中使用它时.我知道我必须有一个 IoC 容器来链接 IStorage
接口的默认实现,但我该怎么做呢?
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?
例如,如果我有以下 xaml 会怎样:
For example, how would it be if I had the following xaml:
<Window
... xmlns definitions ...
>
<Window.DataContext>
<local:SomeViewModel />
</Window.DataContext>
</Window>
在这种情况下,如何正确告诉"WPF 注入依赖项?
How can I correctly 'tell' WPF to inject dependencies in that case?
另外,假设我需要 C# 代码中的 SomeViewModel
实例,我应该怎么做?
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.
我熟悉 StructureMap,但我不是专家.另外,如果有更好/更简单/开箱即用的框架,请告诉我.
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.
推荐答案
我一直在使用 Ninject,发现使用起来很愉快.一切都在代码中设置,语法相当简单,并且有很好的文档(以及大量关于 SO 的答案).
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).
所以基本上是这样的:
创建视图模型,并将IStorage
接口作为构造函数参数:
Create the view model, and take the IStorage
interface as constructor parameter:
class UserControlViewModel
{
public UserControlViewModel(IStorage storage)
{
}
}
使用视图模型的 get 属性创建一个 ViewModelLocator
,它会从 Ninject 加载视图模型:
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
}
}
使 ViewModelLocator
在 App.xaml 中成为应用程序范围的资源:
Make the ViewModelLocator
an application wide resource in App.xaml:
<Application ...>
<Application.Resources>
<local:ViewModelLocator x:Key="ViewModelLocator"/>
</Application.Resources>
</Application>
将UserControl
的DataContext
绑定到ViewModelLocator中对应的属性.
Bind the DataContext
of the UserControl
to the corresponding property in the ViewModelLocator.
<UserControl ...
DataContext="{Binding UserControlViewModel, Source={StaticResource ViewModelLocator}}">
<Grid>
</Grid>
</UserControl>
创建一个继承 NinjectModule 的类,它将设置必要的绑定(IStorage
和视图模型):
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
}
}
在应用程序启动时使用必要的 Ninject 模块(目前为上述模块)初始化 IoC 内核:
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);
}
}
我使用了一个静态 IocKernel
类来保存 IoC 内核的应用程序范围实例,因此我可以在需要时轻松访问它:
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);
}
}
}
这个解决方案确实使用了静态ServiceLocator
(IocKernel
),这通常被认为是一种反模式,因为它隐藏了类的依赖关系.然而,要避免对 UI 类进行某种手动服务查找是非常困难的,因为它们必须有一个无参数的构造函数,并且无论如何您都无法控制实例化,因此您无法注入 VM.至少这种方式可以让你单独测试虚拟机,这是所有业务逻辑所在的地方.
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.
Lucky Likey 通过让 Ninject 实例化 UI 类,提供了摆脱静态服务定位器的答案.答案详情可见这里
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
这篇关于如何在 WPF/MVVM 应用程序中处理依赖注入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!