一尘不染

如何在WPF / MVVM应用程序中处理依赖项注入

c#

我正在启动一个新的桌面应用程序,我想使用MVVM和WPF来构建它。

我也打算使用TDD。

问题是我不知道如何使用IoC容器将依赖项注入生产代码中。

假设我有以下类和接口:

public interface IStorage
{
    bool SaveFile(string content);
}

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

然后我有另一个具有IStorage依赖关系的类,还假设该类是ViewModel或业务类…

public class SomeViewModel
{
    private IStorage _storage;

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

有了这个,我可以轻松地编写单元测试,以确保它们能够正常工作,例如使用模拟等。

问题是要在实际应用程序中使用它。我知道我必须有一个IoC容器,该容器链接该IStorage接口的默认实现,但是我该怎么做呢?

例如,如果我具有以下xaml,将如何处理:

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

在这种情况下,如何正确地“告诉” WPF以注入依赖关系?

另外,假设我需要的实例SomeViewModel从我的cs代码,我应该怎么办呢?

我感到自己完全迷失了这一点,对于任何如何处理最佳方法的示例或指导,我将不胜感激。

我熟悉StructureMap,但我不是专家。另外,如果有更好/更轻松/开箱即用的框架,请告诉我。

提前致谢。


阅读 538

收藏
2020-05-19

共1个答案

一尘不染

我一直在使用Ninject,发现与我合作很愉快。一切都在代码中设置,语法非常简单,并且有一个很好的文档(以及关于SO的大量答案)。

所以基本上它是这样的:

创建视图模型,并将IStorage接口作为构造函数参数:

class UserControlViewModel
{
    public UserControlViewModel(IStorage storage)
    {

    }
}

使用视图属性的get属性创建一个ViewModelLocator,该模型从Ninject加载视图模型:

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

在App.xaml中使ViewModelLocator成为应用程序范围的资源:

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

将UserControl的DataContext绑定到ViewModelLocator中的相应属性。

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

创建一个继承NinjectModule的类,该类将设置必要的绑定(IStorage和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内核:

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

        base.OnStartup(e);
    }
}

我使用了静态IocKernel类来保存IoC内核的应用程序范围的实例,因此可以在需要时轻松访问它:

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,这是所有业务逻辑所在的位置。

如果有人有更好的方法,请分享。

编辑:Lucky
Likey通过让Ninject实例化UI类来提供摆脱静态服务定位器的答案。答案的细节可以在这里看到

2020-05-19