Resolve cyclic dependencies with Castle Windsor
Although of course you should avoid this!
First of all, what is Castle Windsor and cyclic dependencies ?
Castle windsor is a dependency injection tool that allow you to inject real implementation of objects or services, without having to know them.
public class ServiceA
{
}
public class ServiceB
{
public ServiceB(ServiceA a) { }
}var container = new WindsorContainer();
container.Register(
Component.For<ServiceA>(),
Component.For<ServiceB>()
);Like this, when you’ll need a serviceB instance, you can create it using
var serviceB = container.Resolve<ServiceB>();ServiceB will automatically recieve a ServiceA instance as dependency.
No let’s talk about the cyclic dependency issue. Cyclic dependencies occurs when you have a service that has a dependency with another service, that itself has a dependency to it. This can be direct, like A need B, and B need A, or at some deeper level, like A need B, that need C, that need D, ect.. That need A.
public class ServiceA
{
public ServiceA(ServiceB b) { }
}
public class ServiceB
{
public ServiceB(ServiceA a) { }
}
//...
var container = new WindsorContainer();
container.Register(
Component.For<ServiceA>(),
Component.For<ServiceB>()
);
//...
var serviceB = container.Resolve<ServiceB>();
Castle.MicroKernel.Handlers.HandlerException:
Can't create component 'ServiceA' as it has a dependency on 'ServiceB', which also depends on 'ServiceA'.This is of course an architectural error, this is clearly a bad design. You should absolutly avoid this kind of scenario.
Why I needed this ?
In my case, I created this library because I had to manage a migration from Spring to Castle Windsor, but the legacy system was full of cyclic dependencies. First I looked at how to avoid them, but this was impossible without breaking everything.
So, allow Castle to resolve these dependencies was the solution, and that may be usefull to other in some similar cases.
The global idea to resolve cyclic dependencies
A way to resolve this is to use Property dependency injection. Because of that, we can now create an object, without it’s dependencies, and set them latter.
For example, to resolve ServiceA, we can create serviceA, then create serviceB, set A instance to B dependency, and then set B to A instance dependency.
From a code point of view, we have to keep a trace of what we have already instanciated, and look into to find an instance and break the cycle.
internal class CyclicComponentActivator : DefaultComponentActivator
{
public CyclicComponentActivator(ComponentModel model, IKernelInternal kernel, ComponentInstanceDelegate onCreation, ComponentInstanceDelegate onDestruction) : base(model, kernel, onCreation, onDestruction)
{
}
protected override object? Instantiate(CreationContext context)
{
var instance = base.Instantiate(context);
if(instance == null)
return null;
context.SetContextualProperty(Model.Name, instance);
foreach (var service in Model.Services)
{
context.SetContextualProperty(service.FullName, instance);
}
return instance;
}
}
internal static bool TryGetContextualInstance(this CreationContext context, string name, out object? dependency)
{
dependency = context.GetContextualProperty(name);
return dependency != null;
}So we create a custom componentActivator, to set the instance we just created by calling the base method into a kind of list. Using the creationContext allow us to be sure that we keep trace of instances that are only created for this resolution context. Like that, we keep the component lifcycle safe.
Then, we have to tell Castle windsor how to resolve properties dependencies, to break the cycle if needed.
internal class CyclicPropertyResolver : ISubDependencyResolver
{
private readonly IKernel _kernel;
public CyclicPropertyResolver(IKernel kernel)
{
_kernel = kernel;
}
public bool CanResolve(CreationContext context, ISubDependencyResolver contextHandler,
ComponentModel model, DependencyModel dependency)
{
return true;
}
public object? Resolve(CreationContext context, ISubDependencyResolver contextHandler,
ComponentModel model, DependencyModel dependency)
{
return string.IsNullOrEmpty(dependency.ReferencedComponentName) ? ResolveType(context, dependency) : ResolveNamedDependency(context, dependency);
}
private object? ResolveType(CreationContext context, DependencyModel dependency)
{
if(context.TryGetContextualInstance(dependency.TargetType.FullName!, out var instance))
return instance;
return dependency.TargetType.IsGenericType ? ResolveGenericType(context, dependency) : TryResolve(context, dependency);
}
private object? ResolveNamedDependency(CreationContext context, DependencyModel dependency)
{
if(context.TryGetContextualInstance(dependency.ReferencedComponentName, out var instance))
return instance;
return _kernel.HasComponent(dependency.ReferencedComponentName) ? _kernel.GetHandler(dependency.ReferencedComponentName)?.TryResolve(context) : null;
}
private object? ResolveGenericType(CreationContext context, DependencyModel dependency)
{
context.AddGenericArguments(dependency.TargetType);
var resolved = TryResolve(context, dependency);
context.CleanGenericArguments();
return resolved;
}
private object? TryResolve(CreationContext context, DependencyModel dependency)
{
return _kernel.HasComponent(dependency.TargetType)
? _kernel.GetHandler(dependency.TargetType)?.TryResolve(context)
: null;
}
}The code is pretty simple, we create a ISubDependencyResolver, then we just manage how to resolve these dependencies. We first look if they are in the creation context, if yes, we use it, and break the cycle. If not, we resolve it using the same creation context.
When Castle will try to instanciate it, it’ll use the componentActivator we created before. So this new instance will be added to the context.
Finally, we just have to integrate that code with castle by register our custom implementations :
public static IWindsorContainer ResolveCyclicDependencies(this IWindsorContainer container)
{
container.Kernel.Resolver.AddSubResolver(new CyclicPropertyResolver(container.Kernel));
container.Kernel.ComponentModelBuilder.AddContributor(new CyclicActivatorContributor());
return container;
}Sources and package
You can download this package using
dotnet add package CastleWindsor.CyclicResolution
The source are available at https://github.com/remihenache/CastleWindsor.CyclicResolution
