Как конструктор ViewModel получает необходимые интерфейсы? - PullRequest
1 голос
/ 20 февраля 2020

Мой вопрос основан на InventorySampleApp от Microsoft.

ServiceLocator содержит метод Configure(), который регистрирует Services и ViewModels. С помощью метода GetService<T>() мы можем его получить. Например, ProductView.cs:

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

Каждый *ViewModel содержит конструктор с интерфейсом, например:

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

Я не могу понять магию 1017 * что ViewModel использует, чтобы получить такие интерфейсы в своем конструкторе. Таким образом, нет таких строк:

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

Как конструктор ViewModel получает требуемые интерфейсы?

ServiceLocator

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

Ответы [ 2 ]

2 голосов
/ 20 февраля 2020

При использовании внедрения зависимостей создание объектов перемещается в компонент с именем Контейнер внедрения зависимостей (DI) или Контейнер инверсии управления (Io C) . Этот компонент имеет своего рода реестр, который содержит все известные сервисы, которые могут быть созданы. В вашем примере serviceCollection - это реестр.

Теперь, когда компонент A нуждается в экземпляре из реестра, есть две разные опции:

  1. Прямой запрос контейнер для экземпляра, например ServiceLocator.Current.GetService<ProductDetailsViewModel>(). Это известно как шаблон локатора службы (я бы рекомендовал немедленно забыть об этом).
  2. Вместо того, чтобы напрямую запрашивать контейнер, запросите зависимость через конструктор A (например, public A(ProductDetailsViewModel viewModel)).

Второй подход можно продвигать все больше и больше, пока не будет достигнута вершина иерархии приложений - так называемые composition root.

В любом случае, в обоих Кстати, контейнер использует механизм Отражение . Это способ получения метаданных классов, методов, свойств, конструкторов и т. Д. c. Всякий раз, когда у контейнера запрашивается определенный тип (например, ProductDetailsViewModel), он использует отражение, чтобы получить информацию о своем конструкторе.
Как только конструктор разрешен, его зависимости также известны (IProductService, IFilePickerService, ICommonServices). Поскольку эти зависимости зарегистрированы в контейнере (помните serviceCollection), экземпляры могут быть созданы.
Это продолжается и продолжается до тех пор, пока не останется больше зависимостей, и контейнер не сможет начать создавать и создавать все объекты. Наконец, есть экземпляр ProductDetailsViewModel. Если в цепочке построения есть одна зависимость, которая неизвестна контейнеру, создание экземпляра завершается неудачей.

Таким образом, процесс создания экземпляра перемещается из вашего кода в контейнер DI.

2 голосов
/ 20 февраля 2020

Обратите внимание, что метод GetService вызывает ServiceProvider.GetService . Это библиотечный метод, который заботится о вас. Под крышками используется отражение для проверки конструктора запрашиваемого вами типа.

Например, когда вы запрашиваете ProductDetailsViewModel, ServiceLocator может видеть, что ему нужен объект типов IProductService, IFilePickerService, ICommonServices.

Затем он просматривает свой реестр служб. Например, строка

serviceCollection.AddSingleton<IProductService, ProductService>();

регистрирует конкретный тип ProductService для интерфейса IProductService, поэтому, когда ServiceLocator необходимо создать объект IProductService, он будет использовать ProductService object.

Этот процесс называется auto-wiring и более подробно описан в главе 12 книги Стивена ван Дёрсена и моей книги о внедрении зависимостей .

Я не могу понять магию 1033 *, которую ViewModel использует для получения таких интерфейсов в своем конструкторе.

Действительно, сделайте себе одолжение и научитесь Pure DI вместо того, чтобы полагаться на непрозрачные библиотеки, с которыми вам неудобно.

Я никогда раньше не видел этот пример базы кода, но из приведенных здесь примеров он выглядит как заполненный. с запахами кода и анти-паттернами.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...