如何处理 WPF/MVVM 应用程序中的依赖注入 [英] How to handle dependency injection in a WPF/MVVM application

查看:38
本文介绍了如何处理 WPF/MVVM 应用程序中的依赖注入的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在启动一个新的桌面应用程序,我想使用 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>

UserControlDataContext绑定到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.至少这种方式可以让你独立测试 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 应用程序中的依赖注入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆