为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

慕雪6442864 2019-11-07 12:47:59
我正在启动一个新的桌面应用程序,我想使用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,但我不是专家。另外,如果有更好/更轻松/开箱即用的框架,请告诉我。提前致谢。
查看完整描述

3 回答

?
偶然的你

TA贡献1841条经验 获得超3个赞

我一直在使用Ninject,发现与我合作很愉快。一切都在代码中设置,语法非常简单,并且有一个很好的文档。


所以基本上它是这样的:


创建视图模型,并将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,这是所有业务逻辑所在的位置。


查看完整回答
反对 回复 2019-11-07
?
饮歌长啸

TA贡献1951条经验 获得超3个赞

在您的问题中,您设置了DataContextXAML中视图属性的值。这要求您的视图模型具有默认的构造函数。但是,正如您已经指出的那样,这与要在构造函数中注入依赖项的依赖项注入不能很好地配合。


因此,您无法DataContext在XAML中设置属性。相反,您还有其他选择。


如果您的应用程序基于简单的分层视图模型,则可以在应用程序启动时构造整个视图模型分层结构(您必须StartupUri从App.xaml文件中删除属性):


public partial class App {


  protected override void OnStartup(StartupEventArgs e) {

    base.OnStartup(e);

    var container = CreateContainer();

    var viewModel = container.Resolve<RootViewModel>();

    var window = new MainWindow { DataContext = viewModel };

    window.Show();

  }


}

这基于植根于的视图模型的对象图,RootViewModel但是您可以将一些视图模型工厂注入父视图模型,从而允许它们创建新的子视图模型,因此不必固定对象图。这也希望回答您的问题想我需要的实例,SomeViewModel从我的cs代码,我应该怎么办呢?


class ParentViewModel {


  public ParentViewModel(ChildViewModelFactory childViewModelFactory) {

    _childViewModelFactory = childViewModelFactory;

  }


  public void AddChild() {

    Children.Add(_childViewModelFactory.Create());

  }


  ObservableCollection<ChildViewModel> Children { get; private set; }


 }


class ChildViewModelFactory {


  public ChildViewModelFactory(/* ChildViewModel dependencies */) {

    // Store dependencies.

  }


  public ChildViewModel Create() {

    return new ChildViewModel(/* Use stored dependencies */);

  }


}

如果您的应用程序本质上更具动态性,并且可能是基于导航的,那么您将不得不加入执行导航的代码。每次导航到新视图时,都需要创建一个视图模型(从DI容器中),视图本身并将DataContext该视图的设置为该视图模型。您可以先执行此视图,然后再根据视图选择视图模型,也可以先执行视图模型视图模型确定使用哪个视图。MVVM框架通过某种方式提供了此关键功能,您可以将DI容器挂接到视图模型的创建中,但也可以自己实现。我在这里有点含糊,因为根据您的需要,此功能可能会变得非常复杂。这是您从MVVM框架获得的核心功能之一,但是在简单的应用程序中滚动自己的功能将使您很好地了解MVVM框架提供的功能。


由于无法DataContext在XAML中声明,因此失去了一些设计时支持。如果您的视图模型包含一些数据,它将在设计时出现,这可能非常有用。幸运的是,您也可以在WPF中使用设计时属性。一种方法是将以下属性添加到<Window>元素或<UserControl>XAML中:


xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

mc:Ignorable="d"

d:DataContext="{d:DesignInstance Type=local:MyViewModel, IsDesignTimeCreatable=True}"

视图模型类型应具有两个构造函数,默认构造函数用于设计时数据,另一个构造函数用于依赖项注入:


class MyViewModel : INotifyPropertyChanged {


  public MyViewModel() {

    // Create some design-time data.

  }


  public MyViewModel(/* Dependencies */) {

    // Store dependencies.

  }


}

这样,您可以使用依赖项注入并保留良好的设计时支持。


查看完整回答
反对 回复 2019-11-07
  • 3 回答
  • 0 关注
  • 1014 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信