Fed*_*kin 90 c# wpf dependencies dependency-injection mvvm
我正在启动一个新的桌面应用程序,我想使用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
}
}
Run Code Online (Sandbox Code Playgroud)
然后我有另一个IStorage
作为依赖项的类,假设这个类是一个ViewModel或一个业务类......
public class SomeViewModel
{
private IStorage _storage;
public SomeViewModel(IStorage storage){
_storage = storage;
}
}
Run Code Online (Sandbox Code Playgroud)
有了这个,我可以轻松编写单元测试,以确保它们正常工作,使用模拟等.
问题是在实际应用中使用它.我知道我必须有一个链接IStorage
接口默认实现的IoC容器,但我该怎么做呢?
例如,如果我有以下xaml会怎么样:
<Window
... xmlns definitions ...
>
<Window.DataContext>
<local:SomeViewModel />
</Window.DataContext>
</Window>
Run Code Online (Sandbox Code Playgroud)
在这种情况下,如何正确地"告诉"WPF注入依赖项?
另外,假设我需要一个SomeViewModel
来自我的cs
代码的实例,我应该怎么做?
我觉得我完全迷失了,我会感谢任何有关如何处理它的最好方法的例子或指导.
我熟悉StructureMap,但我不是专家.此外,如果有更好/更容易/开箱即用的框架,请告诉我.
提前致谢.
son*_*ard 78
我一直在使用Ninject,发现很高兴能与之合作.一切都在代码中设置,语法相当简单,并且有很好的文档(以及大量的答案).
所以基本上它是这样的:
创建视图模型,并将IStorage接口作为构造函数参数:
class UserControlViewModel
{
public UserControlViewModel(IStorage storage)
{
}
}
Run Code Online (Sandbox Code Playgroud)
使用视图模型的get属性创建一个ViewModelLocator,它从Ninject加载视图模型:
class ViewModelLocator
{
public UserControlViewModel UserControlViewModel
{
get { return IocKernel.Get<UserControlViewModel>();} // Loading UserControlViewModel will automatically load the binding for IStorage
}
}
Run Code Online (Sandbox Code Playgroud)
使ViewModelLocator成为App.xaml中的应用程序范围的资源:
<Application ...>
<Application.Resources>
<local:ViewModelLocator x:Key="ViewModelLocator"/>
</Application.Resources>
</Application>
Run Code Online (Sandbox Code Playgroud)
将UserControl的DataContext绑定到ViewModelLocator中的相应属性.
<UserControl ...
DataContext="{Binding UserControlViewModel, Source={StaticResource ViewModelLocator}}">
<Grid>
</Grid>
</UserControl>
Run Code Online (Sandbox Code Playgroud)
创建一个继承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
}
}
Run Code Online (Sandbox Code Playgroud)
在应用程序启动时使用必要的Ninject模块初始化IoC内核(现在就是上面的模块):
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
IocKernel.Initialize(new IocConfiguration());
base.OnStartup(e);
}
}
Run Code Online (Sandbox Code Playgroud)
我使用静态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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
此解决方案确实使用静态ServiceLocator(IocKernel),它通常被视为反模式,因为它隐藏了类的依赖性.但是,要避免对UI类进行某种手动服务查找是非常困难的,因为它们必须具有无参数构造函数,并且无论如何都无法控制实例化,因此无法注入VM.至少这种方式允许您隔离地测试VM,这是所有业务逻辑所在的位置.
如果有人有更好的方法,请分享.
编辑:幸运Likey通过让Ninject实例化UI类提供了摆脱静态服务定位器的答案.答案的细节可以在这里看到
Mar*_*age 48
在您的问题中,您DataContext
在XAML中设置视图属性的值.这要求您的视图模型具有默认构造函数.但是,正如您所指出的,对于要在构造函数中注入依赖项的依赖项注入,这不能很好地工作.
因此,您无法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();
}
}
Run Code Online (Sandbox Code Playgroud)
这是基于视图模型的对象图,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 */);
}
}
Run Code Online (Sandbox Code Playgroud)
如果您的应用程序本质上更具动态性,并且可能基于导航,则必须挂钩执行导航的代码.每次导航到新视图时,都需要创建视图模型(来自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}"
Run Code Online (Sandbox Code Playgroud)
视图模型类型应该有两个构造函数,设计时数据的默认值和依赖注入的另一个构造函数:
class MyViewModel : INotifyPropertyChanged {
public MyViewModel() {
// Create some design-time data.
}
public MyViewModel(/* Dependencies */) {
// Store dependencies.
}
}
Run Code Online (Sandbox Code Playgroud)
通过这样做,您可以使用依赖注入并保留良好的设计时支持.
Luc*_*key 24
我在这里发布的内容是对sondergard的答案的改进,因为我要讲的不适合评论:)
事实上,我正在引入一个简洁的解决方案,它避免了需要ServiceLocator和StandardKernel
-Instance 的包装器,它在sondergard的Solution中被调用IocContainer
.为什么?如上所述,这些都是反模式.
StandardKernel
随处可见Ninject的神奇之处在于使用StandardKernel
-Method所需的-Instance .Get<T>()
.
除了sondergard之外,IocContainer
你可以创建-Class StandardKernel
内部App
.
只需从App.xaml中删除StartUpUri即可
<Application x:Class="Namespace.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
...
</Application>
Run Code Online (Sandbox Code Playgroud)
这是App.xaml.cs中App的CodeBehind
public partial class App
{
private IKernel _iocKernel;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
_iocKernel = new StandardKernel();
_iocKernel.Load(new YourModule());
Current.MainWindow = _iocKernel.Get<MainWindow>();
Current.MainWindow.Show();
}
}
Run Code Online (Sandbox Code Playgroud)
从现在开始,Ninject还活着,准备战斗:)
DataContext
当Ninject存活时,您可以执行各种注射,例如Property Setter Injection或最常见的一个Constructor Injection.
这就是你将ViewModel注入你Window
的方式的方法DataContext
public partial class MainWindow : Window
{
public MainWindow(MainWindowViewModel vm)
{
DataContext = vm;
InitializeComponent();
}
}
Run Code Online (Sandbox Code Playgroud)
当然,IViewModel
如果你做了正确的绑定,你也可以注入一个,但这不是这个答案的一部分.
如果你需要直接在内核上调用方法(例如.Get<T>()
-Method),你可以让内核自己注入.
private void DoStuffWithKernel(IKernel kernel)
{
kernel.Get<Something>();
kernel.Whatever();
}
Run Code Online (Sandbox Code Playgroud)
如果您需要内核的本地实例,可以将其作为Property注入.
[Inject]
public IKernel Kernel { private get; set; }
Run Code Online (Sandbox Code Playgroud)
虽然这可能非常有用,但我不建议你这样做.请注意,以这种方式注入的对象在构造函数中不可用,因为它稍后会注入.
根据此链接,您应该使用工厂扩展而不是注入IKernel
(DI容器).
在软件系统中使用DI容器的推荐方法是应用程序的组合根是直接触摸容器的单个位置.
如何使用Ninject.Extensions.Factory 在这里也可以是红色的.
And*_*ens 12
我采用"视图优先"的方法,将视图模型传递给视图的构造函数(在其代码隐藏中),该构造函数被分配给数据上下文,例如
public class SomeView
{
public SomeView(SomeViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
}
Run Code Online (Sandbox Code Playgroud)
这取代了基于XAML的方法.
我使用Prism框架来处理导航 - 当某些代码请求显示特定视图时(通过"导航"),Prism将解析该视图(在内部,使用应用程序的DI框架); DI框架将依次解析视图具有的任何依赖关系(在我的示例中为视图模型),然后解析其依赖关系,依此类推.
DI框架的选择几乎是无关紧要的,因为它们基本上都是相同的,即你注册一个接口(或类型)以及你希望框架在找到对该接口的依赖时实例化的具体类型.为了记录,我使用Castle Windsor.
Prism导航需要一些习惯,但是一旦你了解它就非常好,允许你使用不同的视图组合你的应用程序.例如,您可以在主窗口上创建一个Prism"区域",然后使用Prism导航,您可以在该区域内从一个视图切换到另一个视图,例如,当用户选择菜单项或其他任何内容时.
或者看一下MVVM Light之类的MVVM框架.我没有这些经验,所以不能评论他们喜欢用什么.
kid*_*haw 10
安装MVVM Light.
部分安装是创建视图模型定位器.这是一个将视图模型公开为属性的类.然后,可以从IOC引擎返回这些属性的getter实例.幸运的是,MVVM light还包括SimpleIOC框架,但如果您愿意,可以在其他框架中连接.
使用简单的IOC,您可以针对类型注册实现...
SimpleIOC.Default.Register<MyViewModel>(()=> new MyViewModel(new ServiceProvider()), true);
Run Code Online (Sandbox Code Playgroud)
在此示例中,将根据构造函数创建视图模型并传递服务提供者对象.
然后,您创建一个从IOC返回实例的属性.
public MyViewModel
{
get { return SimpleIOC.Default.GetInstance<MyViewModel>; }
}
Run Code Online (Sandbox Code Playgroud)
聪明的部分是,视图模型定位器随后在app.xaml中创建或等效创建为数据源.
<local:ViewModelLocator x:key="Vml" />
Run Code Online (Sandbox Code Playgroud)
您现在可以绑定到其"MyViewModel"属性,以使用注入的服务获取您的viewmodel.
希望有所帮助.对从iPad上的内存编码的任何代码不准确表示歉意.
回答一个旧帖子,但这样做DryIoc
并做我认为很好地使用 DI 和接口(最少使用具体类)。
App.xaml
,在那里我们告诉您要使用的初始视图是什么;我们使用后面的代码而不是默认的 xaml 来做到这一点:StartupUri="MainWindow.xaml"
在 App.xaml 中删除在代码隐藏(App.xaml.cs)中添加override OnStartup
:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
DryContainer.Resolve<MainWindow>().Show();
}
Run Code Online (Sandbox Code Playgroud)这就是启动点;这也是唯一resolve
应该被调用的地方。
配置根(根据 Mark Seeman 的书 .NET 中的依赖注入;应该提到具体类的唯一地方)将在相同的代码隐藏中,在构造函数中:
public Container DryContainer { get; private set; }
public App()
{
DryContainer = new Container(rules => rules.WithoutThrowOnRegisteringDisposableTransient());
DryContainer.Register<IDatabaseManager, DatabaseManager>();
DryContainer.Register<IJConfigReader, JConfigReader>();
DryContainer.Register<IMainWindowViewModel, MainWindowViewModel>(
Made.Of(() => new MainWindowViewModel(Arg.Of<IDatabaseManager>(), Arg.Of<IJConfigReader>())));
DryContainer.Register<MainWindow>();
}
Run Code Online (Sandbox Code Playgroud)MainWindow
;带有 DI 的 ViewModel 构造函数:
public MainWindowViewModel(IDatabaseManager dbmgr, IJConfigReader jconfigReader)
{
_dbMgr = dbmgr;
_jconfigReader = jconfigReader;
}
Run Code Online (Sandbox Code Playgroud)
用于设计的 ViewModel 默认构造函数:
public MainWindowViewModel()
{
}
Run Code Online (Sandbox Code Playgroud)
视图的代码隐藏:
public partial class MainWindow
{
public MainWindow(IMainWindowViewModel vm)
{
InitializeComponent();
ViewModel = vm;
}
public IViewModel ViewModel
{
get { return (IViewModel)DataContext; }
set { DataContext = value; }
}
}
Run Code Online (Sandbox Code Playgroud)
以及在视图 (MainWindow.xaml) 中使用 ViewModel 获取设计实例所需的内容:
d:DataContext="{d:DesignInstance local:MainWindowViewModel, IsDesignTimeCreatable=True}"
Run Code Online (Sandbox Code Playgroud)
因此,我们使用 DryIoc 容器和 DI 获得了一个非常干净和最小的 WPF 应用程序实现,同时保持视图和视图模型的设计实例成为可能。
归档时间: |
|
查看次数: |
61269 次 |
最近记录: |