For some strange (but probably valid) reason Windsor child-containers can access their Parent containers but not the other way around. This means that in order to use services registered in the main container from the new container, we have to set the Parent of the main container rather than that of the new container.
This is painfully inconvenient because a container can only have one Parent.
internal class ProjServices : IProjServices
{
private readonly IKwProject _proj;
private readonly IWindsorContainer _mainCont;
public ProjServices(IKwProject proj, IWindsorContainer mainCont)
{
_mainCont = mainCont;
_proj = proj;
}
public T Resolve<T>()
{
T rett;
//Create new container
var projCont = new WindsorContainer();
//Register new service
projCont.Register(Component.For<IKwProject>().Instance(_proj));
//Set hierarchy
lock (_mainCont)
{
projCont.AddChildContainer(UiContainer); //ui needs project, set parent to projCont
UiContainer.AddChildContainer(_mainCont); //main needs ui, set parent to uiCont
//Resolve using main, which now has access to UI and Project services
try
{
rett = _mainCont.Resolve<T>();
}
finally
{
projCont.RemoveChildContainer(UiContainer);
UiContainer.RemoveChildContainer(_mainCont);
}
}
return rett;
}
private static readonly object UIContainerLock = new object();
private static volatile IWindsorContainer _uiContainer;
private static IWindsorContainer UiContainer
{
get
{
if(_uiContainer==null)
lock(UIContainerLock)
if (_uiContainer == null)
{
//Register the UI services
}
return _uiContainer;
}
}
}
And now if I wanted to use these new containers in even newer containers in the future I think I'd get stuck again due to the one-parent-only thing.... how do I properly do this, please?
UPDATE:
Unit Tests for VS 2010 and 2012:
ServiceTest.zip (798 KB)
https://mega.co.nz/#!z4JxUDoI!UEnt3TCoMFVg-vXKEAaJrhzjxfhcvirsW2hv1XBnZCc
Or to copy&paste:
using System;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ServiceTest
{
/// <summary>
/// A service that doesn't rely on anything else
/// </summary>
public interface IService1
{
}
class Service1 : IService1
{
}
/// <summary>
/// The Project
/// </summary>
public interface IProject
{
string Name { get; }
}
public class Project : IProject
{
public Project(string name)
{
Name = name;
}
public string Name { get; private set; }
}
/// <summary>
/// A Service that relies on a Project
/// </summary>
public interface IService2
{
IProject Project { get; }
string GetProjectName();
}
/// <summary>
/// The implementation shows it also relies on IService3
/// </summary>
public class Service2 : IService2
{
public Service2(IProject project, IService3 service3)
{
Project = project;
Service3 = service3;
}
public IProject Project { get; private set; }
public IService3 Service3 { get; private set; }
public string GetProjectName()
{
return Project.Name;
}
}
/// <summary>
/// IService3 is a Service that also relies on the Project
/// </summary>
public interface IService3
{
IProject Project { get; }
}
public class Service3 : IService3
{
public Service3(IProject project)
{
Project = project;
}
public IProject Project { get; private set; }
}
/// <summary>
/// Class1 uses the service without any dependencies so it will be easy to resolve
/// </summary>
public class Class1
{
public Class1(IService1 service1)
{
Service1 = service1;
}
public IService1 Service1 { get; private set; }
}
/// <summary>
/// Class2 also uses that service, but it also relies on a Project ánd IService2
/// which as you know also relies on the Project and IService3 which also relies on
/// the Project
/// </summary>
public class Class2
{
public Class2(IService1 service1, IProject project, IService2 service2)
{
Service1 = service1;
Project = project;
Service2 = service2;
}
public IProject Project { get; private set; }
public IService1 Service1 { get; private set; }
public IService2 Service2 { get; private set; }
}
/// <summary>
/// Set up the base services
/// </summary>
[TestClass]
public class UnitTestBase
{
protected WindsorContainer Cont;
[TestInitialize]
public void BaseSetup()
{
Cont = new WindsorContainer();
Cont.Register(Component.For<IService1>().ImplementedBy<Service1>().LifestyleTransient());
Cont.Register(Component.For<IService2>().ImplementedBy<Service2>().LifestyleTransient());
Cont.Register(Component.For<IService3>().ImplementedBy<Service3>().LifestyleTransient());
Cont.Register(Component.For<Class1>().LifestyleTransient());
Cont.Register(Component.For<Class2>().LifestyleTransient());
}
[TestMethod]
public void Class1_Resolves()
{
Cont.Resolve<Class1>();
}
}
/// <summary>
/// Set up the base unit tests
/// </summary>
[TestClass]
public class UnitTestClass2Base : UnitTestBase
{
protected void RunTest3Times(Func<string, IWindsorContainer> getContainer)
{
const string projNameBase = "MyProjectName";
Func<int, string> getProjectName = i => projNameBase + i;
for (var i = 0; i < 3; i++)
{
var pName = getProjectName(i);
GetClass2ForProject(getContainer(pName), pName);
}
}
protected void GetClass2ForProject(IWindsorContainer cont, string projName)
{
var c2 = cont.Resolve<Class2>();
Assert.IsTrue(c2.Project.Name == projName);
Assert.IsTrue(c2.Service2.Project.Name == projName);
Assert.IsTrue(c2.Service2.GetProjectName() == projName);
}
}
/// <summary>
/// This will fail on the second request because we cannot
/// overwrite the earlier registration. And iirc containers can't
/// be altered after the first resolve.
/// </summary>
[TestClass]
public class Attempt_1 : UnitTestClass2Base
{
[TestMethod]
public void Class2_Resolves_Project_Scoped_Requests()
{
RunTest3Times(s =>
{
Cont.Register(Component.For<IProject>().Instance(new Project(s)));
return Cont;
});
}
}
/// <summary>
/// It looks like we have to create a new container for every Project
/// So now the question remains; how do we get to keep using the base IService implementations
/// in the container that is scoped for the IProject?
/// </summary>
[TestClass]
public class Attempt_2 : UnitTestClass2Base
{
static IWindsorContainer CreateContainer(IProject p)
{
var ret = new WindsorContainer();
ret.Register(Component.For<IProject>().Instance(p));
return ret;
}
/// <summary>
/// This will fail because the services in the main
/// container can't access the IProject in the new container
/// </summary>
[TestMethod]
public void Class2_Resolves_Project_Scoped_Requests_1()
{
RunTest3Times(s =>
{
//Add the project container as a Child to the Main container
var projCont = CreateContainer(new Project(s));
Cont.AddChildContainer(projCont);
return Cont;
});
}
/// <summary>
/// Doing the previous approach the other way around works.
/// But now we can only resolve one thing at a time
/// </summary>
[TestMethod]
public void Class2_Resolves_Project_Scoped_Requests_2()
{
IWindsorContainer projCont = null;
//Add the Main container as a Child to the project container
// (in other words set the Parent of Main to Project)
// and then resolve using the main container.
//A container can only have one parent at a time so we can only
// resolve one scoped thing at a time.
RunTest3Times(s =>
{
if (projCont != null)
projCont.RemoveChildContainer(Cont);
projCont = CreateContainer(new Project(s));
projCont.AddChildContainer(Cont);
return Cont;
});
}
/// <summary>
/// The only way around that issue seems to be to register all project-dependent
/// services in the new container. Then re-register all original services
/// in the new container and pass the resolving on to the main container;
/// a maintenance nightmare and especially painful for named registrions.
/// </summary>
[TestMethod]
public void Class2_Resolves_Project_Scoped_Requests_3()
{
Func<IProject, IWindsorContainer> createContainer2 = p =>
{
var contNew = new WindsorContainer();
//Pass resolving of the non-dependent services on to the main container.
// this way it will respect it's lifestyle rules and not create new
// instances of services we wanted to use as a singleton etc.
contNew.Register(Component.For<IService1>().UsingFactoryMethod(() => Cont.Resolve<IService1>()).LifestyleTransient());
contNew.Register(Component.For<Class1>().UsingFactoryMethod(() => Cont.Resolve<Class1>()).LifestyleTransient());
//Register the dependent services directly in the new container so they can access the project
contNew.Register(Component.For<IService2>().ImplementedBy<Service2>().LifestyleTransient());
contNew.Register(Component.For<IService3>().ImplementedBy<Service3>().LifestyleTransient());
contNew.Register(Component.For<Class2>().LifestyleTransient());
contNew.Register(Component.For<IProject>().Instance(p));
return contNew;
};
RunTest3Times(s =>
{
var projCont = createContainer2(new Project(s));
return projCont;
});
}
}
}