How does the ViewModel constructor get the required interfaces?

谁都会走 提交于 2020-03-02 17:12:08

问题


My question based on InventorySampleApp by Microsoft.

The ServiceLocator contains method Configure() that register Services and ViewModels. With method GetService<T>() we can get it. For example, ProductView.cs:

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

Each *ViewModel contains constructor with interface, for example:

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

I can't understand the magiс that ViewModel uses to get such interfaces into its constructor. So there are no lines like this:

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

How does the ViewModel constructor get the required interfaces?

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

回答1:


When using dependency injection, the instantiation of objects is moved to a component called Dependency Injection (DI) Container or Inverse of Control (IoC) Container. This component has some kind of registry that contains all known services that can be instantiated. In your example, the serviceCollection is that registry.

Now, whenever a component A needs an instance from the registry, there are two different options:

  1. Directly ask the container for an instance, e. g. ServiceLocator.Current.GetService<ProductDetailsViewModel>(). This is known as the Service Locator Pattern (I'd recommend to forget this immediately).
  2. Rather than asking the container directly, request the dependency via constructor of A (e. g. public A(ProductDetailsViewModel viewModel)).

The second approach can be pushed more and more upwards until the top of the application hierarchy is reached - the so called composition root.

Anyways, in both ways, the container uses the mechanism of Reflection. It is a way of retrieving metadata of classes, methods, properties, constructors, etc. Whenever the container is asked for a certain type (e. g. ProductDetailsViewModel), he uses reflection to get information about its constructor.
Once the constructor is resolved, its dependencies are known as well (IProductService, IFilePickerService, ICommonServices). Since these dependencies are registered within the container (remember the serviceCollection), instances can be created.
This goes on and on until there are no more dependencies and the container can start instantiating and composing all the objects. Finally, there is an instance of ProductDetailsViewModel. If there is one dependency within the construction chain that is unknown to the container, the instantiation fails.

So basically, the process of instantiation is moved away from your code into the DI container.




回答2:


Notice that the GetService method calls into ServiceProvider.GetService. This is a library method that takes care of things for you. Underneath the covers, it uses reflection to examine the constructor of the type you request.

For example, when you request a ProductDetailsViewModel, the ServiceLocator can see that it needs object of the types IProductService, IFilePickerService, ICommonServices.

It then looks into its registry of services. For example, the line

serviceCollection.AddSingleton<IProductService, ProductService>();

registers the concrete type ProductService against the interface IProductService, so whenever the ServiceLocator needs to create an IProductService object, it'll use a ProductService object.

This process is called auto-wiring and is described in more details in chapter 12 of Steven van Deursen's and my book about Dependency Injection.

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

Indeed, do yourself the favour and learn Pure DI instead of relying on opaque libraries that you don't feel comfortable with.

I've never seen that example code base before, but from the examples posted here, it looks like it's filled with code smells and anti-patterns.



来源:https://stackoverflow.com/questions/60318881/how-does-the-viewmodel-constructor-get-the-required-interfaces

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!