ViewModel 构造函数如何获取所需的接口?

How does the ViewModel constructor get the required interfaces?

我的问题基于 Microsoft InventorySampleApp

ServiceLocator 包含注册服务和 ViewModel 的方法 Configure()。使用方法GetService<T>()我们可以得到它。例如,ProductView.cs

ViewModel = ServiceLocator.Current.GetService<ProductDetailsViewModel>();

每个*ViewModel包含带接口的构造函数,例如:

public ProductDetailsViewModel(IProductService productService, IFilePickerService filePickerService, ICommonServices commonServices)

我无法理解 ViewModel 用来将此类接口引入其构造函数的 magiс。所以没有这样的行:

... = new ProductDetailsViewModel(productService, filePickerService, commonServices)

ViewModel构造函数如何获取需要的接口?

服务定位器

public class ServiceLocator : IDisposable
{
    static private readonly ConcurrentDictionary<int, ServiceLocator> _serviceLocators = new ConcurrentDictionary<int, ServiceLocator>();

    static private ServiceProvider _rootServiceProvider = null;

    static public void Configure(IServiceCollection serviceCollection)
    {
        serviceCollection.AddSingleton<ISettingsService, SettingsService>();
        serviceCollection.AddSingleton<IDataServiceFactory, DataServiceFactory>();
        serviceCollection.AddSingleton<ILookupTables, LookupTables>();
        serviceCollection.AddSingleton<ICustomerService, CustomerService>();
        serviceCollection.AddSingleton<IOrderService, OrderService>();
        serviceCollection.AddSingleton<IOrderItemService, OrderItemService>();
        serviceCollection.AddSingleton<IProductService, ProductService>();

        serviceCollection.AddSingleton<IMessageService, MessageService>();
        serviceCollection.AddSingleton<ILogService, LogService>();
        serviceCollection.AddSingleton<IDialogService, DialogService>();
        serviceCollection.AddSingleton<IFilePickerService, FilePickerService>();
        serviceCollection.AddSingleton<ILoginService, LoginService>();

        serviceCollection.AddScoped<IContextService, ContextService>();
        serviceCollection.AddScoped<INavigationService, NavigationService>();
        serviceCollection.AddScoped<ICommonServices, CommonServices>();

        serviceCollection.AddTransient<LoginViewModel>();

        serviceCollection.AddTransient<ShellViewModel>();
        serviceCollection.AddTransient<MainShellViewModel>();

        serviceCollection.AddTransient<DashboardViewModel>();

        serviceCollection.AddTransient<CustomersViewModel>();
        serviceCollection.AddTransient<CustomerDetailsViewModel>();

        serviceCollection.AddTransient<OrdersViewModel>();
        serviceCollection.AddTransient<OrderDetailsViewModel>();
        serviceCollection.AddTransient<OrderDetailsWithItemsViewModel>();

        serviceCollection.AddTransient<OrderItemsViewModel>();
        serviceCollection.AddTransient<OrderItemDetailsViewModel>();

        serviceCollection.AddTransient<ProductsViewModel>();
        serviceCollection.AddTransient<ProductDetailsViewModel>();

        serviceCollection.AddTransient<AppLogsViewModel>();

        serviceCollection.AddTransient<SettingsViewModel>();
        serviceCollection.AddTransient<ValidateConnectionViewModel>();
        serviceCollection.AddTransient<CreateDatabaseViewModel>();

        _rootServiceProvider = serviceCollection.BuildServiceProvider();
    }

    static public ServiceLocator Current
    {
        get
        {
            int currentViewId = ApplicationView.GetForCurrentView().Id;
            return _serviceLocators.GetOrAdd(currentViewId, key => new ServiceLocator());
        }
    }

    static public void DisposeCurrent()
    {
        int currentViewId = ApplicationView.GetForCurrentView().Id;
        if (_serviceLocators.TryRemove(currentViewId, out ServiceLocator current))
        {
            current.Dispose();
        }
    }

    private IServiceScope _serviceScope = null;

    private ServiceLocator()
    {
        _serviceScope = _rootServiceProvider.CreateScope();
    }

    public T GetService<T>()
    {
        return GetService<T>(true);
    }

    public T GetService<T>(bool isRequired)
    {
        if (isRequired)
        {
            return _serviceScope.ServiceProvider.GetRequiredService<T>();
        }
        return _serviceScope.ServiceProvider.GetService<T>();
    }

    #region Dispose
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (_serviceScope != null)
            {
                _serviceScope.Dispose();
            }
        }
    }
    #endregion

请注意 GetService 方法调用 ServiceProvider.GetService。这是一个为您处理事情的库方法。在幕后,它使用 反射 来检查您请求的类型的构造函数。

例如,当你请求一个ProductDetailsViewModel时,ServiceLocator可以看到它需要IProductServiceIFilePickerServiceICommonServices类型的对象.

然后它查看其服务注册表。例如,行

serviceCollection.AddSingleton<IProductService, ProductService>();

在接口 IProductService 上注册了具体类型 ProductService,所以只要 ServiceLocator 需要创建一个 IProductService 对象,它就会使用 ProductService对象。

这个过程称为自动布线,在 Steven van Deursen 和我的 book about Dependency Injection.

的第 12 章中有更详细的描述

I can't understand the magiс that ViewModel uses to get such interfaces into its constructor.

确实,帮自己一个忙,学习 Pure DI 而不是依赖您不习惯的不透明库。

我以前从未见过该示例代码库,但从此处发布的示例来看,它似乎充满了代码异味和反模式。

使用依赖注入时,对象的实例化被移动到名为依赖注入(DI)容器反向控制(IoC)容器[的组件中。 =41=]。该组件具有某种注册表,其中包含可以实例化的所有已知服务。在您的示例中,serviceCollection 是该注册表。

现在,每当组件 A 需要来自注册表的实例时,有两种不同的选择:

  1. 直接向容器要一个实例,e。 G。 ServiceLocator.Current.GetService<ProductDetailsViewModel>()。这被称为 服务定位器模式 (我建议立即忘记它)。
  2. 与其直接询问容器,不如通过 A 的构造函数请求依赖项(例如 public A(ProductDetailsViewModel viewModel))。

可以将第二种方法越来越向上推,直到到达应用程序层次结构的顶部 - 所谓的 composition root

反正这两种方式,容器都使用了Reflection的机制。这是一种检索 类、方法、属性、构造函数等元数据的方法。每当容器被要求提供某种类型(例如 ProductDetailsViewModel)时,他都会使用反射来获取有关其构造函数的信息。
一旦解析了构造函数,它的依赖关系也就已知了(IProductServiceIFilePickerServiceICommonServices)。由于这些依赖项已在容器中注册(记住 serviceCollection),因此可以创建实例。
这一直持续到没有更多的依赖关系并且容器可以开始实例化和组合所有对象。最后,有一个 ProductDetailsViewModel 的实例。 如果构建链中有一个容器不知道的依赖项,则实例化失败。

所以基本上,实例化过程从您的代码转移到 DI 容器中。