I am developing an MVC-ish (possibly more like MVP) desktop application, using Unity, which has a main UI thread and periodically runs async tasks in the background. The main window's UI events are handled by MainController
. When the user opens a new window, MainController
will create a new child controller and pass in whatever dependencies it needs. When a timer in the main window ticks, it calls a method on MainController
which starts an async task. There is task synchronization in MainController
so I don't have more than one aysnc task running at a time.
I'm injecting two service facades and the async task scheduler into MainController
. The two service facades have one service which they each depend on.
The facade services and task scheduler service look like this:
public class FacadeOne : IFacadeOne
{
private readonly IFirstService firstService;
private readonly ISecondService secondService;
public FacadeOne(IFirstService firstService, ISecondService secondService)
{
this.firstService = firstService;
this.secondService = secondService;
}
}
public class FacadeTwo : IFacadeTwo
{
private readonly IFirstService firstService;
private readonly IThirdService thirdService;
public FacadeTwo(IFirstService firstService, IThirdService thirdService)
{
this.firstService = firstService;
this.thirdService = thirdService;
}
}
public class TaskScheduler : ITaskScheduler
{
private readonly IFacadeOne facadeOne;
private readonly IFacadeTwo facadeTwo;
public TaskScheduler(IFacadeOne facadeOne, IFacadeTwo facadeTwo)
{
this.facadeOne = facadeOne;
this.facadeTwo = facadeTwo;
}
}
MainController
looks like this
public class MainController
{
private readonly IFacadeOne facadeOne;
private readonly IFacadeTwo facadeTwo;
private readonly ITaskScheduler taskScheduler;
public MainController(IFacadeOne facadeOne, IFacadeTwo facadeTwo, ITaskScheduler taskScheduler)
{
this.facadeOne = facadeOne;
this.facadeTwo = facadeTwo;
this.taskScheduler = taskScheduler;
}
}
FacadeOne
and FacadeTwo
both have dependencies on FirstService
.
In my composition root, I create an instance of MainController
. When I create that instance, I want the following to happen (here is a diagram which hopefully makes it clearer):
facadeOne
and facadeTwo
parameters of MainController
should each use the same instance of ServiceOne
.facadeOne
and facadeTwo
parameters which are passed into the taskScheduler
parameter when it is resolved should be different instances than the facadeOne
and facadeTwo
parameters which are passed into MainController
's constructor.taskScheduler
parameter is resolved, its facadeOne
and facadeTwo
parameters should use the same instance of FirstService
as each other, but it should be a different instance that the one used by MainController
's facadeOne
and facadeTwo
parameters.taskScheduler
parameter is resolved, its facadeOne
and facadeTwo
parameters should use different instances of SecondService
and ThirdService
(and also FirstService
as stated above) than those instances used by MainController
's facadeOne
and facadeTwo
parameters.Is there an easy way to do this in Unity using lifetime managers, rather than creating a convoluted and error-prone sequence of Resolve
s, RegisterInstance
s, and temporary variables?
I see two one ways of doing it, the second to be avoided!
Named Registration with ContainerControlledLifetimeManager
If you don't mind doubling some registration with named registrations, you can do that:
container.RegisterType<IFirstService, FirstService>("MainThread", new ContainerControlledLifetimeManager());
container.RegisterType<ISecondService, SecondService>("MainThread", new ContainerControlledLifetimeManager());
container.RegisterType<IThirdService, ThirdService>("MainThread", new ContainerControlledLifetimeManager());
container.RegisterType<IFirstService, FirstService>("TaskScheduler", new ContainerControlledLifetimeManager());
container.RegisterType<ISecondService, SecondService>("TaskScheduler", new ContainerControlledLifetimeManager());
container.RegisterType<IThirdService, ThirdService>("TaskScheduler", new ContainerControlledLifetimeManager());
container.RegisterType<IFacadeOne, FacadeOne>("MainThread", new ContainerControlledLifetimeManager(),
new InjectionConstructor(
new ResolvedParameter<IFirstService>("MainThread"),
new ResolvedParameter<ISecondService>("MainThread")));
container.RegisterType<IFacadeTwo, FacadeTwo>("MainThread", new ContainerControlledLifetimeManager(),
new InjectionConstructor(
new ResolvedParameter<IFirstService>("MainThread"),
new ResolvedParameter<IThirdService>("MainThread")));
container.RegisterType<IFacadeOne, FacadeOne>("TaskScheduler", new ContainerControlledLifetimeManager(),
new InjectionConstructor(
new ResolvedParameter<IFirstService>("TaskScheduler"),
new ResolvedParameter<ISecondService>("TaskScheduler")));
container.RegisterType<IFacadeTwo, FacadeTwo>("TaskScheduler", new ContainerControlledLifetimeManager(),
new InjectionConstructor(
new ResolvedParameter<IFirstService>("TaskScheduler"),
new ResolvedParameter<IThirdService>("TaskScheduler")));
container.RegisterType<ITaskScheduler, TaskScheduler>("TaskScheduler", new ContainerControlledLifetimeManager(),
new InjectionConstructor(
new ResolvedParameter<IFacadeOne>("TaskScheduler"),
new ResolvedParameter<IFacadeTwo>("TaskScheduler")));
container.RegisterType<MainController>(new ContainerControlledLifetimeManager(),
new InjectionConstructor(
new ResolvedParameter<IFacadeOne>("MainThread"),
new ResolvedParameter<IFacadeTwo>("MainThread"),
new ResolvedParameter<ITaskScheduler>("TaskScheduler")));
MainController imWhatYouWanted = container.Resolve<MainController>();
Note1: You could remove all the "MainThread" names (not the registration, but simply make it not-named- and it would work the same. Same with the registration for ITaskScheduler
itself, you could simply not name it, but you would still need to use names in it's ResolvedParameters
.
Note2: You could use HierarchicalLifetimeManager
instead of ContainerControlledLifetimeManager
. They behave the same if you don't use child containers.
Hack I don't recommend
Another way you could do it, which I don't recommend unless you can absolutely not used named registrations, is to : use HierarchicalLifetimeManager
instead of ContainerControlledLifetimeManager
, combined with a child container:
container.RegisterType<IFirstService, FirstService>(new HierarchicalLifetimeManager());
container.RegisterType<ISecondService, SecondService>(new HierarchicalLifetimeManager());
container.RegisterType<IThirdService, ThirdService>(new HierarchicalLifetimeManager());
container.RegisterType<IFacadeOne, FacadeOne>(new HierarchicalLifetimeManager(),
new InjectionConstructor(
new ResolvedParameter<IFirstService>(),
new ResolvedParameter<ISecondService>()));
container.RegisterType<IFacadeTwo, FacadeTwo>(new HierarchicalLifetimeManager(),
new InjectionConstructor(
new ResolvedParameter<IFirstService>(),
new ResolvedParameter<IThirdService>()));
IUnityContainer childContainer = container.CreateChildContainer();
childContainer.RegisterType<ITaskScheduler, TaskScheduler>(new HierarchicalLifetimeManager(),
new InjectionConstructor(
new ResolvedParameter<IFacadeOne>(),
new ResolvedParameter<IFacadeTwo>()));
// Resolve at registration time == Bad.
// You could do a work around too, but that's another lesson!
ITaskScheduler taskScheduler = childContainer.Resolve<ITaskScheduler>();
container.RegisterInstance<ITaskScheduler>(taskScheduler);
container.RegisterType<MainController>(new HierarchicalLifetimeManager(),
new InjectionConstructor(
new ResolvedParameter<IFacadeOne>(),
new ResolvedParameter<IFacadeTwo>(),
new ResolvedParameter<ITaskScheduler>()));
MainController imWhatYouShouldntWant = container.Resolve<MainController>();
With that second solution, by using HierarchicalLifetimeManager
, and having it resolved on the child container, Unity won't consider anything (with that LifetimeManager) when you resolve on the parent.
Note3: Good job providing an image, it made it easy to understand what you wanted!