【问题标题】:How to move from ServiceLocator to Dependency Injection? Specific example如何从 ServiceLocator 迁移到依赖注入?具体例子
【发布时间】:2026-02-10 14:00:01
【问题描述】:

问题正在从ServiceLocator 反模式转移到Dependency Injection。鉴于本人经验不足,无法将DI原理转移到现在实现的代码上。

总结

摘要部分是可选的阅读。您可能想对某事发表评论,建议。

该程序的主要目的是合并特定信息的占位符字段的过程。信息的数量使得需要有基础设施。比如表单、服务、数据库。我对这项任务有一些经验。我设法基于WinForms 创建了一个类似的程序。它甚至可以工作!但就模式、维护、可扩展性和性能而言,代码很糟糕。重要的是要了解编程是一种爱好。这不是主要的教育或工作。

WinForms 上实现所描述的任务的体验非常糟糕。我开始研究模式和新平台。起点是UWPMVVM。需要注意的是,绑定机制很神奇。

途中的第一个问题独立解决了。它与通过ShellPage 中的NavigationViewShellViewModel 连接的UWP 中的导航有关。除了创建一个NavigationService。它基于来自Windows Template Studio 的模板。

由于有一个有效的WinForms 程序及其反模式方向,所以有时间和愿望正确地做所有事情

现在我面临一个架构问题。称为ServiceLocator(或ViewModelLocator)。我在Microsoft 的示例中找到了它,包括Windows Template Studio 的模板。在这样做的过程中,我又落入了反模式陷阱。如上所述,我不想再这样了。

作为解决方案的第一件事是dependency injection。鉴于本人经验不足,无法将DI原理转移到现在实现的代码上。

当前实现

应用程序UWP 的起点是app.xaml.cs。重点是将控制权转移到ActivationService。它的任务是将Frame 添加到Window.Current.Content 并导航到默认页面-MainPageMicrosoft documentation.

ViewModelLocator 是一个单例。第一次调用它的属性会调用构造函数。

private static ViewModelLocator _current;
public static ViewModelLocator Current => _current ?? (_current = new ViewModelLocator());
// Constructor
private ViewModelLocator(){...}

使用ViewModelLocatorView (Page) 是这样的,ShellPage:

private ShellViewModel ViewModel => ViewModelLocator.Current.ShellViewModel;

使用ViewModelLocatorViewModel 类似,ShellViewModel

 private static NavigationService NavigationService => ViewModelLocator.Current.NavigationService;

转向 DI

ShellViewModel 具有来自ViewModelLocatorNavigationService,如上所示。在这一点上我怎么能去DI?事实上,程序很小。现在是摆脱反模式的好时机。

代码

ViewModelLocator

private static ViewModelLocator _current;
public static ViewModelLocator Current => _current ?? (_current = new ViewModelLocator());

private ViewModelLocator()
{
    // Services
    SimpleIoc.Default.Register<NavigationService>();

    // ViewModels and NavigationService items
    Register<ShellViewModel, ShellPage>();
    Register<MainViewModel, MainPage>();
    Register<SettingsViewModel, SettingsPage>();
}

private void Register<TViewModel, TView>()
    where TViewModel : class
    where TView : Page
{
    SimpleIoc.Default.Register<TViewModel>();
    NavigationService.Register<TViewModel, TView>();
}

public ShellViewModel ShellViewModel => SimpleIoc.Default.GetInstance<ShellViewModel>();
public MainViewModel MainViewModel => SimpleIoc.Default.GetInstance<MainViewModel>();
public SettingsViewModel SettingsViewModel => SimpleIoc.Default.GetInstance<SettingsViewModel>();

public NavigationService NavigationService => SimpleIoc.Default.GetInstance<NavigationService>();

ShellPage:页面

private ShellViewModel ViewModel => ViewModelLocator.Current.ShellViewModel;

public ShellPage()
{
    InitializeComponent();

    // shellFrame and navigationView from XAML
    ViewModel.Initialize(shellFrame, navigationView);
}

ShellViewModel : ViewModelBase

private bool _isBackEnabled;
private NavigationView _navigationView;
private NavigationViewItem _selected;

private ICommand _itemInvokedCommand;
public ICommand ItemInvokedCommand => _itemInvokedCommand ?? (_itemInvokedCommand = new RelayCommand<NavigationViewItemInvokedEventArgs>(OnItemInvoked));

private static NavigationService NavigationService => ViewModelLocator.Current.NavigationService;

public bool IsBackEnabled
{
    get => _isBackEnabled;
    set => Set(ref _isBackEnabled, value);
}

public NavigationViewItem Selected
{
    get => _selected;
    set => Set(ref _selected, value);
}

public void Initialize(Frame frame, NavigationView navigationView)
{
    _navigationView = navigationView;
    _navigationView.BackRequested += OnBackRequested;

    NavigationService.Frame = frame;
    NavigationService.Navigated += Frame_Navigated;
    NavigationService.NavigationFailed += Frame_NavigationFailed;
}

private void OnItemInvoked(NavigationViewItemInvokedEventArgs args)
{
    if (args.IsSettingsInvoked)
    {
        NavigationService.Navigate(typeof(SettingsViewModel));
        return;
    }

    var item = _navigationView.MenuItems.OfType<NavigationViewItem>().First(menuItem => (string)menuItem.Content == (string)args.InvokedItem);
    var pageKey = GetPageKey(item);
    NavigationService.Navigate(pageKey);
}
private void OnBackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args)
{
    NavigationService.GoBack();
}

private void Frame_Navigated(object sender, NavigationEventArgs e)
{
    IsBackEnabled = NavigationService.CanGoBack;
    if (e.SourcePageType == typeof(SettingsPage))
    {
        Selected = _navigationView.SettingsItem as NavigationViewItem;
        return;
    }

    Selected = _navigationView.MenuItems
        .OfType<NavigationViewItem>()
        .FirstOrDefault(menuItem => IsMenuItemForPageType(menuItem, e.SourcePageType));
}
private void Frame_NavigationFailed(object sender, NavigationFailedEventArgs e)
{
    throw e.Exception;
}

private bool IsMenuItemForPageType(NavigationViewItem item, Type sourcePageType)
{
    var pageKey = GetPageKey(item);
    var navigatedPageKey = NavigationService.GetNameOfRegisteredPage(sourcePageType);
    return pageKey == navigatedPageKey;
}

private Type GetPageKey(NavigationViewItem item) => Type.GetType(item.Tag.ToString());

更新 1

ServiceLocatorViewModelLocator 之间的相等性我错了吗?

调用ServiceLocator(或ViewModelLocator

本质上,当前任务是连接ViewViewModelNavigationService 超出了此任务的范围。所以不应该在ViewModelLocator吗?

【问题讨论】:

  • 你想做DI还是IOC?如您的示例代码所示,我没有看到构造函数注入的路径。
  • @Maess 我想从“坏”的 ServiceLocator 转移到“好”的解决方案。我发现的第一个,也许是唯一一个是依赖注入。这就是它结束的地方。如果这些是不同的、不相容的东西,为什么要放在一起提到。

标签: c# mvvm dependency-injection uwp service-locator


【解决方案1】:

正如@Maess 所说,您(现在)面临的最大挑战是将静态依赖项重构为构造函数注入。例如,您的 ShellViewModel 应该具有如下构造函数:

public ShellViewModel(INavigationService navigation)

一旦你这样做了,你就可以设置一个 DI 框架(比如 NInject),其中包含你所有的依赖项(有点像你的 SimpleIoC 东西),并且,理想情况下,从容器中拉出一个根对象(它构造了其他所有东西) )。通常这是应用程序的主视图模型。

我已经在 WPF 和 UWP 的多个项目上成功完成了这项工作,而且效果很好。您唯一需要注意的是在运行时创建视图模型时(就像您经常做的那样),通过注入工厂来完成。

【讨论】:

    最近更新 更多