首页 > 解决方案 > ViewModel 构造函数如何获取所需的接口?

问题描述

我的问题基于Microsoft 的InventorySampleApp

ServiceLocator包含Configure()注册服务和视图模型的方法。通过方法GetService<T>()我们可以得到它。例如ProductView.cs

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

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

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

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

... = 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

标签: c#dependency-injectioninversion-of-controlviewmodel

解决方案


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

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

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

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

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

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


推荐阅读