Proposal: Separate Castle.DynamicProxy dependency from ABP
Hi everyone,
I would like to discuss separating the Castle Windsor / Castle.DynamicProxy dependency from ABP.
My long-term goal is to make ABP compatible with NativeAOT.
There are several reasons why I think this is worth considering:
- Castle has performance overhead compared to other IoC.
- Castle is no longer actively maintained.
- It does not integrate cleanly with modern technologies such as .NET Aspire.
- NativeAOT compatibility is very difficult while relying on runtime proxy generation.
I know this topic has come up before:
https://github.com/aspnetboilerplate/aspnetboilerplate/issues/5604
Current progress
As a proof of concept, I successfully separated the built-in interceptors that depend on Castle.DynamicProxy into a separate package named Abp.Interception.Castle.
Instead of moving all interceptor implementations (which would duplicate code and make maintenance difficult), I only moved the registrar classes (for example, AuthorizationInterceptorRegistrar).
This allows the built-in interceptor implementations to remain shared while only the Castle-specific registration logic lives in the new package.
To make this possible, I introduced a new abstraction named IAbpInvocation, which replaces Castle.DynamicProxy.IInvocation.
Interceptor implementation
Instead of using runtime proxy generation, I implemented interception using a C# Source Generator.
The generated code injects the interceptor pipeline directly into the target classes at compile time.
Although there is a much simpler implementation, I intentionally chose a more complex internal implementation in order to preserve source-level compatibility as much as possible.
Compatibility
The good news is that almost all existing interceptor implementations continue to work.
In most cases, the only required change is replacing:
Castle.DynamicProxy.IInvocation
with
IAbpInvocation
The required change is minimal:
- public void Intercept(IInvocation invocation)
+ public void Intercept(IAbpInvocation invocation)You can see the actual changes here:
Replace IInvocation with IAbpInvocation diff
However, since the main ABP package would no longer expose Castle.DynamicProxy.IInvocation, this would still be a breaking change.
I also think it should be possible to provide a source generator (not implemented yet) that automatically rewrites IInvocation references to IAbpInvocation, minimizing migration effort.
There may also need to be a startup extension such as UseCastle() to enable the Castle-based implementation. I'm still considering the best design for that.
Questions
I'd like to hear your thoughts.
Would you support the following direction?
- Separate the
Castle.DynamicProxydependency from the core ABP package. - Introduce
IAbpInvocationinto the ABP package as the common abstraction. - Eventually remove the Castle dependency from the core package completely.
Does this overall direction make sense?
I also have a couple of design questions:
- Do you agree with moving the
Castle.DynamicProxydependency into Abp.Interception.Castle? - Should the built-in interceptor implementations remain in the core ABP package as shared/common code, or would it be better to move them entirely into Abp.Interception.Castle?
Working repository
My work-in-progress repository:
https://github.com/neominky/aspnetboilerplate/
Compile-time interception sample:
Tests:
I'd really appreciate your feedback before I continue with this work.
Source: aspnetboilerplate/aspnetboilerplate