Problem with Castle Windsor resolution in ASP.NET Core
Security is often considered a vertical skill, nothing is far more from the truth
Castle Windsor is a beautiful library for implementing inversion of control, but sometimes problem arise when it is used in projects that start with Full Framework and must be converted to ASP.NET Core during their lifetime. To make this work, an interdependency library is typically used to allow ASP.NET Core infrastructure to resolve dependencies using Castle. This approach helps avoid issues when replacing Castle with other libraries, since Castle is both powerful and complex, allowing for many customizations in dependency resolution. Since it’s not always easy to remove it and make room for new libraries the usual solution is to keep using Castle Winsor.
Unfortunately, sometimes we encounter problems. For example, in a project converted to ASP.NET Core where I added Blazor, I encountered the following error:
Castle.MicroKernel.CircularDependencyException
HResult=0x80131500
Message=Dependency cycle has been detected when trying to resolve component 'Microsoft.AspNetCore.Components.Server.Circuits.DefaultCircuitAccessor@9a501952-efa9-4ce8-ac54-762628d2515e'.
The resolution tree that resulted in the cycle is the following:
Component 'Microsoft.AspNetCore.Components.Server.Circuits.DefaultCircuitAccessor@9a501952-efa9-4ce8-ac54-762628d2515e' resolved as dependency of
component 'System.Func`2[[System.IServiceProvider, System.ComponentModel, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a],[Microsoft.AspNetCore.Components.Server.Circuits.Circuit, Microsoft.AspNetCore.Components.Server, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]]@44dc1b0f-8c85-4bec-ba6b-061ff6455162' resolved as dependency of
component 'Microsoft.AspNetCore.Components.Server.Circuits.DefaultCircuitAccessor@9a501952-efa9-4ce8-ac54-762628d2515e' which is the root component being resolved.
This is the kind of bug that left you with lots of question in your head because it is an error resolving internal classes of ASP.NET core.
The primary difference between the standard ASP.NET Core dependency injection engine and Castle is that Castle, by default, resolves public properties as dependencies. Therefore, if I have a class with a public property, that public property is a potential dependency. Castle checks if a component of that type has been registered. If registered, it is resolved and assigned to the property; otherwise, the property is left null without throwing an error. Now lets have a look to the class that generates the above problem.
Figure 1: Class of ASP.NET CORE that gnerates circular dependency.
The issue above occurs because some internal ASP.NET Core classes do not account for dependency resolution on public properties. The ASP.NET Core engine assumes that the Circuit property will not be resolved by the inversion of control engine since the default .NET Core engine does not resolve properties. However, if I use Castle internally, the container will attempt to resolve this dependency, and since Circuit is a valid component that depends on ICircuitAccessor, we have a circular dependency.
As these classes are internal and we cannot easily interfere with their registration, the solution must be different. Castle Windsor allows us to decorate a property with a DoNotWire attribute that instructs the resolver to ignore this property.
public class UniqueHostPropertiesDependenciesModelInspector : PropertiesDependenciesModelInspector
{
public UniqueHostPropertiesDependenciesModelInspector(IConversionManager converter) : base(converter)
{
}
protected override void InspectProperties(ComponentModel model)
{
if (model.Implementation.FullName.StartsWith("Microsoft.AspNetCore."))
{
// Avoid inspection of properties due to Castle resolving properties, thus creating a circular dependency.
return;
}
base.InspectProperties(model);
}
}
The class described above inherits from a Castle base class responsible for dissecting the dependency and finding all optional public property dependencies. As you can see, the customization is straightforward. If the class being registered has a type that begins with “Microsoft.AspNetCore”, which includes all internal and general ASP.NET Core framework classes, it immediately returns without calling the base method. This approach simply prevents registering public properties as dependencies for all classes within the Microsoft.AspNetCore
namespace. Since all ASP.NET infrastructure assumes that no public property is used as dependency, this solves lots of anomalies.
You can easily register this class in the Windsor container by removing the existing instance and adding this new one:
var defaultPropertyInspector = _container.Kernel.ComponentModelBuilder
.Contributors
.OfType<PropertiesDependenciesModelInspector>()
.Single();
_container.Kernel.ComponentModelBuilder.RemoveContributor(defaultPropertyInspector);
var conversionManager = _container.Kernel.GetConversionManager();
var customPropertyResolver = new UniqueHostPropertiesDependenciesModelInspector(conversionManager);
_container.Kernel.ComponentModelBuilder.AddContributor(customPropertyResolver);
By doing this, we resolve the circular dependency issue and prevent internal or other ASP.NET Core framework classes from being registered differently than the framework itself expects.
With this custom solution in place, your ASP.NET Core application should now work seamlessly with Castle Windsor without encountering circular dependency issues. Furthermore, you can keep using Castle Windsor’s powerful and flexible dependency resolution features without having to refactor your entire project.
In conclusion, Castle Windsor remains a powerful and versatile library for inversion of control. By using the above approach, you can mitigate potential issues with circular dependencies when integrating Castle Windsor with ASP.NET Core, allowing you to maximize the benefits of both libraries while maintaining compatibility and stability within your project.
Remember that this solution may not apply to every scenario, and you should always evaluate and test your specific use case before implementing it in your project. Nonetheless, the provided workaround demonstrates how to leverage Castle Windsor’s flexibility and customization capabilities to address potential issues with dependency resolution when working with the ASP.NET Core framework.
Gian Maria