Как правильно взаимодействовать сервису окон, di-контейнеру, чтобы можно было прокидывать сервис окон в другие vm, но сам сервис имел доступ к di

Помогите разобраться с последовательностью и проблемкой, котелок уже сгорел)

  1. Сделал свой di-контейнер - здесь регистрируются зависимости, их сопоставления(интерфейсы и имплементации).
  2. Сделал свой сервис окон - здесь регистрируются view и viewModel (создает vm по типу и показывает окно).

Какой должна быть правильная последовательность действий?

  1. Создаю контейнер (var container = DiContainer)
  2. Регистрирую сервис окон (container.RegisterDependency<IServiceView, ServiceView>())
  3. Получаю сервис окон в поле (var serviceView = container.GetDependency<IServiceView>)
  4. В сервисе окон регистрирую view и viewModel (serviceView.RegisterView<MainWindow, MainViewModel>())
  5. Открываю окно (var view = serviceView.ShowView<MainViewModel>(); view.Show())

Все как бы хорошо работает, но если мне нужно открыть другие окна через главное окно то есть нужно прокидывать ServiceView как зависимость, чтобы воспользоваться методом открытия окна. Получается ViewModel должна иметь зависимость ctor(IServiceView serviceView), то есть при создании vm в сервисе окон, этот сервис должен иметь ссылку на di-контейнер чтобы обращаться к методу получения нужных зависимостей, тогда получается, что в контейнере есть сервис окон, а в сервисе окон должен быть контейнер - цикличность ссылок вроде как.

Даже если не цикличность, то если прокидывается сервис окон в MainViewModel требующая iServiceView, ServiceView будет новым экземпляром так как создан через Activator.CreateInstance и не имеет зарегистрированных view с viewModel. То есть все потеряно. Как быть?

Слышал о какой-то фабрике классов, почекал но не понял как реализовать.

Подскажите правильную последовательность действий, архитектуру что с чем должен правильно взаимодействовать, что в чем должно лежать для взаимодействия (например, если нужна тут фабрика)


Ответы (1 шт):

Автор решения: Vladimir

Transient & Single

DI контейнер минимально должен уметь:

container.RegisterDependency<...>().AsTransient()
container.RegisterDependency<...>().AsSingle()

У вас, я так понял, Transient зависимости - каждый раз создается новая. Нужно еще реализовать Single зависимости - контейнер должен выдавать всегда один и тот же инстанс. Собственно, это та причина, по которой паттерн синглтон при DI отсутствует в принципе.

container.RegisterDependency<...>().AsSingle().WithId("SomeId")
container.RegisterDependency<...>().WithArgument(() => container.FromResolve<...>("SomeId")).AsTransient()

Айдишники - пригодятся для случая когда нужно несколько инстансов одного и того же класса, но не более. (А еще там может быть два вида айдишников - для интерфейса и для конкретного типа, в общем, айдишники - это весело...)

Циклические ссылки

  • A(B b) и B(A a) - так нельзя, контейнер не разрулит.
  • A(B b) и b.SetA(A a) - так можно, если метод вызываем из пользовательского кода. Даже если так:
container.RegisterDependency<B>().WhenInstantiated(b => b.SetA(container.FromResolve<A>()));

Фабрики

"Чистые" фабрики в DI могут выглядеть как-то так:

interface ISomeFactory {
    IAbstract Create(ISomethingElse somethingElse, int someParam);
}

class SomeFactory : ISomeFactory {
    ISomething _something;
    public SomeFactory(ISomething something) => _something = something;
    public IAbstract Create(ISomethingElse somethingElse, int someParam) => new Concrete(_something, somethingElse, someParam);
}

Если фабрика более универсальна, и заранее в нее нельзя передать все зависимости (даже если просто из-за их количества), то можно туда передать контейнер как зависимость. Фабрика формально считается принадлежащей уровню контейнера, если хотите. Одна из задач фабрик - не дать контейнеру просочиться в пользовательский код, но сама она его использовать может.

Адекватное решение

  1. var container = DiContainer
  2. container.RegisterDependency<IServiceView, ServiceView>().AsSingle()
  3. var serviceView = container.GetDependency
  4. serviceView.RegisterView<MainWindow, MainViewModel>()
  5. var view = serviceView.Create(); view.Show()

Ваш ServiceView так-то можно считать фабрикой. Он имеет право принимать в себя контейнер. При создании ViewModel можно резолвить зависимости через контейнер, и это тогда задача контейнера выдать один и тот же IServiceView во все ViewModel.

Простое решение

При создании ViewModel внутри ServiceView просто передавать туда this, и только оставшиеся параметры резолвить через контейнер

Неадекватное решение

Инжектить в ServiceView по "чистой" фабрике на каждую ViewModel

container.RegisterDependency<IServiceView, ServiceView>().AsSingle()
container.RegisterDependency<IViewModeFactory, MyViewModelFactory>().AsSingle()
container.RegisterDependency<IViewModeFactory, MyOtherViewModelFactory>().AsSingle()

А конструктор ServiceView, может выглядет так, например:

ServiceView(List<IViewModeFactory> viewModelFactories) { ... }

Каждая фабрика будет принимать все зависимости, нужные для ViewModel, в свой конструктор. Дальше, при создании ServiceView контейнер инжектит все фабрики с интерфейсом IViewModeFactory.

Неадекватность решения в том, что на каждую ViewModelпридется делать фабрику. А ведь еще есть View. Проще сказать, что ServiceView - это фабрика и передать туда контейнер.

→ Ссылка