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

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

问题描述

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



我也打算使用TDD。



问题是我不知道我应该如何使用IoC容器来将我的依赖项注入到我的生产代码上。



假设我有一个愚蠢的类和接口:

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

public class存储:IStorage
{
public bool SaveFile(string content){
//使用StreamWriter
保存文件}
}

然后我有另一个类有 IStorage 作为依赖,假设此类是ViewModel或业务类...

  public class SomeViewModel 
{
private IStorage _storage;

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

有了这个我可以轻松地编写单元测试来确保它们正常工作,使用mocks等。



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



例如,如果我有以下xaml,那将如何?

 < Window 
... xmlns定义...
>
< Window.DataContext>
< local:SomeViewModel />
< /Window.DataContext>
< / Window>

如何正确地告诉WPF注入依赖关系?



另外,假设我需要从我的 cs 代码中的一个 SomeViewModel 我应该怎么做?



我觉得我完全失去了这一点,我会感谢任何一个例子或指导如何处理它的最佳方式。 >

我熟悉StructureMap,但我不是专家。另外,如果有更好/更容易/开箱即用的框架,请让我知道。



提前感谢。

解决方案

我一直在使用Ninject,发现很高兴与之合作。一切都是在代码中设置的,语法是非常简单的,它有一个很好的文档(在SO上有很多答案)。



所以基本上是这样的: / p>

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

  class UserControlViewModel 
{
public UserControlViewModel(IStorage storage)
{

}
}
/ pre>

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

  class ViewModelLocator 
{
public UserControlViewModel UserControlViewModel
{
get {return IocKernel.Get&UserControlViewModel>();} //加载UserControlViewModel将自动加载IStorage的绑定
}
}

使ViewModelLocator App.xaml中的应用范围广泛的资源:

 < ;应用程序...> 
< Application.Resources>
<本地: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()
{
绑定< IStorage>()。到< Storage>()。InSingletonScope(); //每次重新使用相同的存储空间

绑定< UserControlViewModel>()。ToSelf()。InTransientScope(); //每次创建新的实例
}
}

初始化IoC内核在应用程序启动时使用必要的Ninject模块(现在的一个):

  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,这是所有业务逻辑的地方。



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

编辑:
幸运的Likey提供了一个答案来摆脱静态服务定位器,让Ninject实例化UI类。 此处可以看到答案的细节


I am starting a new desktop application and I want to build it using MVVM and WPF.

I am also intending to use TDD.

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
    }
}

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.

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 may I to do it?

For example, how would it be if I had the following xaml:

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

How can I correctly 'tell' WPF to inject dependencies in that case?

Also, suppose I need an instance of SomeViewModel from my cs code, how should I do it?

I feel I´m completely lost in this, I would appreciate any example or guidance of how is the best way to handle it.

I am familiar with StructureMap, but I´m not an expert. Also, if there is a better/easier/out-of-the-box framework, please let me know.

Thanks in advance.

解决方案

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

这篇关于如何在WPF / MVVM应用程序中处理依赖注入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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