В одном своем проекте использую UnityContainer - расширяемый DI-контейнер (dependency injection container) от Misrosoft. И понадобилось мне передавать в конструкторы создаваемых контейнером сервисов объекты (DataContext), временем жизни которых я управлял сам. То есть мне понадобилось примерно следующее:
// работаем с одним контекстом данных
DataContext dx1 = container.Resolve();
container.Resolve(/*dx1*/); // конструктор сервиса: public Service1(DataContext dx)
container.Resolve(/*dx1*/); // конструктор сервиса: public Service2(DataContext dx)
// работаем с другим контекстом
DataContext dx2 = container.Resolve();
container.Resolve(/*dx2*/);
Оказалось, что для этой довольно типичной задачи UnityContainer не предлагает однозначного решения. Это обстоятельство порождает множество подходов, которые можно найти в интернете. Одним из таких решений является разрешение (resolve) объекта с переопределением его параметра:
DataContext dx1 = container.Resolve();
Service1 srv1 = container.Resolve(new ParameterOverride("dx", dx1));
Но здесь нужно быть крайне осторожным, т.к. при изменении имени параметра, контейнер без какого-либо предупреждения инициализирует сервис им вновь созданным объектом DataContext, а наш объект будет попросту проигнорирован.
Многие другие найденные в интернете решения немного модернизированные базировались на указанном подходе. При этом имена параметров извлекались при помощи рефлексии, а реализация заворачивалась в метод расширение (extension).
Другой, приемлемый, на мой взгляд, подход - вынос инициализации сервисов контекстом в отдельный метод:
DataContext dx1 = container.Resolve();
Service1 srv1 = container.Resolve();
Service2 srv2 = container.Resolve();
srv1.Init(dx1);
srv2.Init(dx1);
Я же задействовал потомка контейнера (ChildContainer), что, как мне показалось, является наиболее простым и естественным способом для решения поставленной задачи. Вот что у меня получилось:
DataContext dx1 = container.Resolve();
IUnityContainer childContainer = container.CreateChildContainer();
childContainer.RegisterInstance(dx1, new ExternallyControlledLifetimeManager()); // !!!
Service1 srv1 = childContainer.Resolve();
Service2 srv2 = childContainer.Resolve();
childContainer.Dispose();
Идея проста: В потомке контейнера регистрируем необходимую инстанцию DataContext, создаем с его помощью объект сервиса (Resolve) и в конце заметаем за собой следы (Dispose). Обратите внимание на указание внешнего контроллера времени жизни объекта dx1 (ExternallyControlledLifetimeManager) - тем самым мы исключаем DataContext из утилизации при вызове метода childContainer.Dispose().
Этот код лег в основу метода-расширения, которое я использовал в своем проекте:
public static class UnityExtensions
{
public static T xResolve(this IUnityContainer container, DataContext dx) {
using (IUnityContainer childContainer = container.CreateChildContainer())
{
childContainer.RegisterInstance(dx, new ExternallyControlledLifetimeManager());
return childContainer.Resolve();
}
}
}
Теперь вызовы выглядят так:
DataContext dx1 = container.Resolve(); // Первая инстанция
Service1 srv1 = container.xResolve(dx1);
Service2 srv2 = container.xResolve(dx1);
DataContext dx3 = container.Resolve(); // Вторая инстанция
Service1 srv3 = container.xResolve(dx3);
PS: К концу написания статьи натолкнулся на еще один способ реализации необходимого функционала - использование переопределения зависимости DependencyOverride:
container.Resolve(new DependencyOverride(dx1));
Здесь объект DataContext встраивается в граф объектов контейнера и используется везде, где есть зависимость этого типа.