问题
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:
- 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). - 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