Take a simple example - the base of cities. Let's make an interface of the city handler, which, in particular, should save information about the city. It turns out something like this:

interface ICitiesHandler { Save(City city); ... } 

And somewhere we describe its implementation (it does not interest us). Registered it in a DI-container. I am using MVVMLight . When added to a project, he creates such a class (in which we register our handler):

 public class ViewModelLocator { static ViewModelLocator() { ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); if (ViewModelBase.IsInDesignModeStatic) { // Create design time view services and models //SimpleIoc.Default.Register<IDataService, DesignDataService>(); } else { SimpleIoc.Default.Register<ICitiesHandler, CitiesHandler>(); // Регистрируем обработчик } } /// <summary> /// Initializes a new instance of the ViewModelLocator class. /// </summary> public ViewModelLocator() { } public static void Cleanup() { // TODO Clear the ViewModels } } 

}

Further we will make ViewModel city. Its dependency will be our ICitiesHandler - it must accept the changes from the user and save them.

 class CityVM : BaseVM { private readonly ICitiesHandler _handler; private readonly City _city; public CityVM(City city, ICitiesHandler handler) { } ... public void Save() { // Применение изменений ... Save(_city); } } 

It seems everything is done. But now in some of the code sections I need to create a new city. And all is well, if I have an instance of the ICitiesHandler implementation, but do not pass it as a dependency on all the objects that cities can create? In theory, I would like to do this:

 var city = new CityVM(); // Далее наполняем данными и сохраняем ... city.Save(); 

For this, for CityVM I can make a default constructor, in which I can transfer new City() as a city model, but how can I resolve the ICitiesHandler dependency?

 public CityVM() : base(new City(), ???) { } 

How to be? I have two options.

  1. All VMs should be requested from the DI container, but then the entire ViewModel layer will refer to ServiceLocator , even if it is a static class, even a singleton (which, by the way, some people also consider antipattern, along with ServiceLocator )

  2. Or all the functionality for resolving dependencies should be concluded in one place, to which all ViewModel have access - this is BaseVM . We make it a Resolve<T>() method and then the designer of the new CityVM will look like this:

     public CityVM() : base(new City(), Resolve<ICitiesHandler>()) 

Which is also not very pleasant to me, because we hide the dependence on the programmer

In the book by Mark Siman “Dependency Injection in .NET” he says that SerciceLocator can be used, but carefully. At the same time, encapsulate it as close as possible to the CompositionRoot .

How to be? What am I doing wrong? Or am I using the wrong framework for this?

  • Before you write the answer, I have a few leading questions to you. 1. Why do you consider the transfer of ICitiesHandler as a dependency to all objects that can create cities as a problem? 2. Why do you need ICitiesHandler when creating a new city? You do not need to save the city in the database immediately after its creation? Instead, you can create a separate interface-factory, which is taken as dependency in all places where you need to create cities. - Dmitry Shevchenko
  • 1. It seems to me very uncomfortable. After all, a city (town) can create, say, a subject of the Russian Federation, a region of a subject, etc. At the same time, the region of the subject can be created by the subject of the Russian Federation. It turns out it is necessary to transfer ICitiesHandler throughout the chain. Doubtful perspective. 2. I did not understand. I need to call the Save method so that this entry is created. ICitiesHandler will ICitiesHandler immediately in the database or not, I should not be interested in what is happening under the hood of the specific implementation of ICitiesHandler not a problem in this context - Donil
  • In Siman ServiceLocator in antipatterns, where did you find there about "you can use"? The author of the book cannot forbid using something, but he clearly does not recommend it. His recommendation is a non-static IoC container that the entire graph (tree) of dependencies in one place creates. That's all. Everything else is transmitted down the tree. - ixSci
  • @ixSci, yes, he says that he attributes it to anti-patterns, but at the same time there is a recommendation about its use, if you nevertheless decided - Donil

2 answers 2

but not to transfer it as a dependency to all objects that can create cities?

That's right, transfer.

Judging by this code snippet, CityVM is a CityVM model of the City data object:

 var city = new CityVM(); // Далее наполняем данными и сохраняем ... city.Save(); 

If this is true, CityVM should not contain the Save() method. This method should be in the view model of the screen on which you are creating (or editing) a city. And this view-screen model should receive an ICitiesHandler dependency. In any case, the place of creation of the city should be as localized as possible: both at the UI level (specific screen) and at the code level (specific view model of the screen or service).

  • > If this is true that CityVM should not contain a method> Save () As conceived, CityVM displayed to the user and it has a SaveCommand command. You can, of course, wrap CityVM in ViewModel for editing, but it seems to me that this is already superfluous - Donil

Try everything you need to pass through the designer, so it’s normal practice for the right places to register factories. For example, the same factory of view models. IoC is not obliged, and simply cannot, resolve all dependencies. So factories, protected factory methods perfectly complement IoC in practice.

The point of IoC is to create a graph of objects at the top level, and lower if you use it. then it essentially turns into a ServiceLocator, and if something needs to be created lower in levels, then IoC should not transfer itself, but instantiate factories.

Also try to separate the editing of the model and the code for its reading / storage. Although this is easier said than done if you need a validation tied to a database, and validation in WPF is inconvenient

  • @ minus , justify minus - Donil
  • can you know more about the factory? And what's inconvenient in WPF validation? - Donil
  • one
    @Donil Many people think that IoC is a “universal factory”, b expect all problems to be solved by it (for example, registration by key). IoC is a configurable factory, but is only good for the top level, otherwise it turns into a ServceLocator with its flaws. If you need to create objects on the lower levels by parameters, then this should be done by factories that need to be registered in IoC (instead of registration tricks, you need to register factories by key). Of course, this factory can accept a link to IoC. The factory can be Func <>, even though it looks clumsy. - vitidev
  • @Donil WPF validation with its ValidationRule and IDataErrorInfo forces us to validate according to their requirements for implementation or to fence a bicycle. Take the check "essence is in the database". If we push it into the ValidationRule, then we will not be able to reuse this validation just in the code. And if in IDataErrorInfo, then we bring knowledge about storage in CityVM, which is wrong. create the model correctly, open the form, click ok, close the form and save the model in the calling code. That is, not a model (in its view model) should know how to save itself. You can run validators, but these are crutches. - vitidev
  • @Donil And this is a general theory. I myself consider the level of view mode as the top level, so I calmly pass IoC on view models. Shattered view model is still one level for me. That is, I try to transfer less to the model, but if I need a container, I transfer it. The code that lies below the level and is called from the IoC view models does not fall - there are already only explicit dependencies that can be seen from the signature. - vitidev