| name | use-interceptors-and-dynamic-proxy |
| description | ABP cross-cutting behavior via Castle dynamic proxies and IAbpInterceptor. USE FOR: how validation, unit of work, authorization, auditing, feature interceptors are selected; interceptor not running; custom interceptor via Services.OnRegistered. DO NOT USE FOR: module lifecycle (define-application-modules); audit-log persistence (configure-audit-logging); feature values (manage-settings-and-features); services without interception (register-and-replace-services). |
| license | MIT |
Using Interceptors and Dynamic Proxies
ABP attaches IAbpInterceptor implementations while services are registered, then the Autofac/Castle integration creates either an interface proxy or a class proxy. The call must cross that proxy for an interceptor to run.
When to Use
- Diagnose why validation,
[UnitOfWork], [Authorize], [Audited], or [RequiresFeature] is not taking effect on a service call.
- Add reusable before/after behavior around application or domain services.
- Select services for interception through
IServiceCollection.OnRegistered.
- Inspect the invoked method, arguments, target, or return value through
IAbpMethodInvocation.
When Not to Use
- Module lifecycle and ordinary registrations — use define-application-modules.
- Audit-log storage, contributors, and entity history — use configure-audit-logging.
- Feature definitions and values — use manage-settings-and-features.
- Service exposure, replacement, keyed services, or decoration — use register-and-replace-services.
How it works
The invocation contract
Implement IAbpInterceptor.InterceptAsync(IAbpMethodInvocation) directly or derive from AbpInterceptor. Call ProceedAsync() exactly once to continue the chain. The invocation also exposes Arguments, ArgumentsDictionary, GenericArguments, TargetObject, Method, and ReturnValue.
using System.Diagnostics;
using Microsoft.Extensions.Logging;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
public class ExecutionTimeInterceptor : AbpInterceptor, ITransientDependency
{
private readonly ILogger<ExecutionTimeInterceptor> _logger;
public ExecutionTimeInterceptor(ILogger<ExecutionTimeInterceptor> logger)
{
_logger = logger;
}
public override async Task InterceptAsync(IAbpMethodInvocation invocation)
{
var stopwatch = Stopwatch.StartNew();
try
{
await invocation.ProceedAsync();
}
finally
{
_logger.LogInformation(
"{MethodName} completed in {ElapsedMilliseconds} ms",
invocation.Method.Name,
stopwatch.ElapsedMilliseconds);
}
}
}
Omitting ProceedAsync() intentionally short-circuits the target and all later interceptors. Use try/finally when after-logic must run for both success and failure.
Register a custom interceptor
Choose an explicit marker or attribute. A marker interface keeps the selector simple and avoids intercepting unrelated services.
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Collections;
using Volo.Abp.DependencyInjection;
public interface IExecutionTimeEnabled
{
}
public static class ExecutionTimeInterceptorRegistrar
{
public static void RegisterIfNeeded(IOnServiceRegistredContext context)
{
if (typeof(IExecutionTimeEnabled).IsAssignableFrom(context.ImplementationType))
{
context.Interceptors.TryAdd<ExecutionTimeInterceptor>();
}
}
}
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.OnRegistered(ExecutionTimeInterceptorRegistrar.RegisterIfNeeded);
}
ExecutionTimeInterceptor is conventionally registered because it implements ITransientDependency. Register the OnRegistered callback early enough to observe the target services; PreConfigureServices is the safe module-level location.
How the built-in interceptors are selected
Each built-in module installs an OnRegistered callback. Its registrar tests the implementation type and adds an interceptor only when needed:
| Concern | Registrar trigger |
|---|
| Validation | Type implements IValidationEnabled and is not in DynamicProxyIgnoreTypes. |
| Unit of work | UnitOfWorkHelper.IsUnitOfWorkType(...) returns true and the type is not ignored. |
| Authorization | Type or any method has [Authorize] and the type is not ignored. |
| Auditing | The type is auditable by default, has [Audited], implements IAuditingEnabled, or has an audited method, subject to ignore rules. |
| Features | Type or any method has [RequiresFeature] and the type is not ignored. |
The registrar decision only attaches the interceptor to the service registration. The interceptor itself performs the runtime check and behavior.
Hard conditions for interception
All of these must hold:
- The implementation is registered through ABP DI while the relevant
OnRegistered callback is active.
- The service is resolved from DI and invoked through the resolved proxy;
new bypasses the proxy.
- The registrar selects the implementation and adds the interceptor type.
- The call uses an exposed interface, or the class-proxy target method is overridable (
virtual, non-sealed class).
- Class interception has not been disabled with
DisableAbpClassInterceptors, a selector, or DisableAbpFeaturesAttribute.DisableInterceptors.
- The Autofac integration is present.
AbpAutofacModule depends on AbpCastleCoreModule; the latter registers the async interceptor adapter.
For an interface proxy, inject and call the interface. For a class proxy, make the intercepted public/protected method virtual. A non-virtual class method executes normally but Castle cannot override it.
Validation
- Resolve the target from an
IServiceScope; do not instantiate it directly.
- Test both the selected interface path and, if supported, the concrete class path.
- Add a deterministic signal in the custom interceptor and verify it occurs before/after the target once.
- Verify exception behavior: the target exception still propagates and
finally logic runs.
- For a missing built-in behavior, confirm the module installed its registrar, the type matches the registrar trigger, and the method call crosses a proxy.
Common Pitfalls
- A class method is not
virtual — class proxies cannot intercept it. Inject an exposed interface or make the method overridable.
- The object was created with
new — only the container-resolved proxy carries interceptors.
- The service was registered before the callback — register selector callbacks in
PreConfigureServices.
- The selector checks the service interface instead of
ImplementationType — IOnServiceRegistredContext.ImplementationType is what built-in registrars inspect.
ProceedAsync() is called zero or multiple times — zero short-circuits execution; multiple calls execute the downstream chain repeatedly.
- The type is deliberately ignored — check
DynamicProxyIgnoreTypes, DisableAbpClassInterceptors, and DisableAbpFeaturesAttribute before changing business code.