WPF、MVVM IoC:服务定位器模式的替代方案。需要依赖在View后面的代码
WPF, MVVM IoC: Alternative to Service Locator Pattern. Need dependency in View code behind
按照几个指南,我使用 WPF .NET 4.7.1 和 MVVM-Light 得到了如下所示的应用程序布局。顺便说一句,我是 WPF 的新手。
App.xaml:
<Application x:Class="My.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewmodel="clr-namespace:My.ViewModel"
StartupUri="View\MainView.xaml">
<Application.Resources>
<ResourceDictionary>
<viewmodel:ViewModelLocator x:Key="Locator" />
</ResourceDictionary>
</Application.Resources>
将 "ViewModelLocator" class 注册为资源并将 WPF 启动设置为 "View/MainView.xaml"。
MainView.xaml:
<Window x:Class="My.View.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.DataContext>
<Binding Path="Main" Source="{StaticResource Locator}"/>
</Window.DataContext>
ViewModelLocator 像服务定位器模式一样使用。这里将 DataContext 设置为我的 "MainViewModel"(未显示)。尽管我不喜欢这样,但我可以在 WPF XAML 上下文中接受它。但是现在事实证明我需要在视图的代码隐藏中依赖(而不是 ViewModel)。
MainView.cs:
public partial class MainView : INotifyPropertyChanged
{
public MainView()
{
// Need to access dependency here.
}
}
现在我可以直接在该构造函数中调用 ViewModelLocator 并从我的 IoC 容器中解析 - 但后来我完全放弃并接受了该模式。
我当然更愿意将依赖项注入到 ctor 中,如果可能的话,我也会完全离开 ViewModelLocator 并在此处注入 ViewModel。
所以问题是,是否有一些标准方法可以指示 WPF 应用程序使用我的容器?如果是,是否建议沿着这条路走而不使用 ViewModelLocator 东西?
你绝对不必使用 ViewModelLocator
(旁注,服务定位器模式最近作为一种反模式受到了相当多的批评,但我会让你形成自己的观点). MVVM Light 和其他库基本上可以让您访问工具包。您不需要使用所有的工具,您应该只使用您的特定域所必需的工具。
在 ViewModelLocator
之外,还有两种模式,称为 ViewModel First
和 View First
,两者各有优缺点。然而,两者都提供了一种解耦代码的方法,这意味着以后切换起来并不困难。
至于在没有服务定位器的情况下使用 MVVM Light 构建应用程序,我对 View First 方法的实现看起来像这样。
我听说 ViewModel First
是首选的意见,但我发现 View First 对于测试驱动开发 (TDD) 来说更简单
App.xaml.cs(后面的应用代码)
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var bootStrapper = new BootStrapper();
//Container Builder
var container = bootStrapper.BootStrap();
var mainWindow = container.Resolve<MainWindow>();
mainWindow.Show();
}
}
BootStrapper.cs(本例中我使用的是 AutoFac,但您可以轻松替换。)
public class BootStrapper
{
public IContainer BootStrap()
{
var builder = new ContainerBuilder();
builder.RegisterType<MainWindow>().AsSelf();
builder.RegisterType<MainWindowViewModel>().AsSelf();
return builder.Build();
}
}
MainWindowViewModel.cs
//I rolled my own ViewModelBase, but you can use MVVM Light's ViewModelBase
public class MainWindowViewModel : ViewModelBase
{
public string DisplayProgram
{
get { return _displayProgram; }
//MVVM Light's ViewModelBase uses RaisePropertyChanged();
set { _displayProgram = value; OnPropertyChanged(); }
}
public void Initialize()
{
//Called from view code behind.
}
}
MainWindow.xaml.cs(主窗口代码隐藏)
//When MainWindow.Show()..
public partial class MainWindow : Window
{
private readonly MainWindowViewModel _viewModel;
//Container resolves dependencies
public MainWindow(MainWindowViewModel viewModel)
{
//Let base Window class do its thing.
InitializeComponent();
//Handle loaded event
Loaded += MainWindowLoaded;
//Hold on to the MainWindowViewModel, and set it as the windows DataContext
_viewModel = viewModel;
DataContext = _viewModel;
}
private void MainWindowLoaded(object sender, RoutedEventArgs e)
{
_viewModel.Initialize();
}
}
按照几个指南,我使用 WPF .NET 4.7.1 和 MVVM-Light 得到了如下所示的应用程序布局。顺便说一句,我是 WPF 的新手。
App.xaml:
<Application x:Class="My.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewmodel="clr-namespace:My.ViewModel"
StartupUri="View\MainView.xaml">
<Application.Resources>
<ResourceDictionary>
<viewmodel:ViewModelLocator x:Key="Locator" />
</ResourceDictionary>
</Application.Resources>
将 "ViewModelLocator" class 注册为资源并将 WPF 启动设置为 "View/MainView.xaml"。
MainView.xaml:
<Window x:Class="My.View.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.DataContext>
<Binding Path="Main" Source="{StaticResource Locator}"/>
</Window.DataContext>
ViewModelLocator 像服务定位器模式一样使用。这里将 DataContext 设置为我的 "MainViewModel"(未显示)。尽管我不喜欢这样,但我可以在 WPF XAML 上下文中接受它。但是现在事实证明我需要在视图的代码隐藏中依赖(而不是 ViewModel)。
MainView.cs:
public partial class MainView : INotifyPropertyChanged
{
public MainView()
{
// Need to access dependency here.
}
}
现在我可以直接在该构造函数中调用 ViewModelLocator 并从我的 IoC 容器中解析 - 但后来我完全放弃并接受了该模式。
我当然更愿意将依赖项注入到 ctor 中,如果可能的话,我也会完全离开 ViewModelLocator 并在此处注入 ViewModel。
所以问题是,是否有一些标准方法可以指示 WPF 应用程序使用我的容器?如果是,是否建议沿着这条路走而不使用 ViewModelLocator 东西?
你绝对不必使用 ViewModelLocator
(旁注,服务定位器模式最近作为一种反模式受到了相当多的批评,但我会让你形成自己的观点). MVVM Light 和其他库基本上可以让您访问工具包。您不需要使用所有的工具,您应该只使用您的特定域所必需的工具。
在 ViewModelLocator
之外,还有两种模式,称为 ViewModel First
和 View First
,两者各有优缺点。然而,两者都提供了一种解耦代码的方法,这意味着以后切换起来并不困难。
至于在没有服务定位器的情况下使用 MVVM Light 构建应用程序,我对 View First 方法的实现看起来像这样。
我听说 ViewModel First
是首选的意见,但我发现 View First 对于测试驱动开发 (TDD) 来说更简单
App.xaml.cs(后面的应用代码)
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var bootStrapper = new BootStrapper();
//Container Builder
var container = bootStrapper.BootStrap();
var mainWindow = container.Resolve<MainWindow>();
mainWindow.Show();
}
}
BootStrapper.cs(本例中我使用的是 AutoFac,但您可以轻松替换。)
public class BootStrapper
{
public IContainer BootStrap()
{
var builder = new ContainerBuilder();
builder.RegisterType<MainWindow>().AsSelf();
builder.RegisterType<MainWindowViewModel>().AsSelf();
return builder.Build();
}
}
MainWindowViewModel.cs
//I rolled my own ViewModelBase, but you can use MVVM Light's ViewModelBase
public class MainWindowViewModel : ViewModelBase
{
public string DisplayProgram
{
get { return _displayProgram; }
//MVVM Light's ViewModelBase uses RaisePropertyChanged();
set { _displayProgram = value; OnPropertyChanged(); }
}
public void Initialize()
{
//Called from view code behind.
}
}
MainWindow.xaml.cs(主窗口代码隐藏)
//When MainWindow.Show()..
public partial class MainWindow : Window
{
private readonly MainWindowViewModel _viewModel;
//Container resolves dependencies
public MainWindow(MainWindowViewModel viewModel)
{
//Let base Window class do its thing.
InitializeComponent();
//Handle loaded event
Loaded += MainWindowLoaded;
//Hold on to the MainWindowViewModel, and set it as the windows DataContext
_viewModel = viewModel;
DataContext = _viewModel;
}
private void MainWindowLoaded(object sender, RoutedEventArgs e)
{
_viewModel.Initialize();
}
}