mirror of https://github.com/abpframework/abp.git
committed by
GitHub
3 changed files with 198 additions and 7 deletions
@ -1,7 +0,0 @@ |
|||||
# Dynamic Proxying / Interceptors |
|
||||
|
|
||||
This document is planned to be written later. |
|
||||
|
|
||||
## See Also |
|
||||
|
|
||||
* [Video tutorial](https://abp.io/video-courses/essentials/interception) |
|
||||
@ -0,0 +1,194 @@ |
|||||
|
# Interceptors |
||||
|
|
||||
|
ABP provides a powerful interception system that allows you to execute custom logic before and after method calls without modifying the original method code. This is achieved through **dynamic proxying** and is extensively used throughout the ABP framework to implement cross-cutting concerns. ABP's interception is implemented on top of the [Castle DynamicProxy](https://www.castleproject.org/projects/dynamicproxy/) library. |
||||
|
|
||||
|
## What is Dynamic Proxying / Interception? |
||||
|
|
||||
|
**Interception** is a technique that allows executing additional logic before or after a method call without directly modifying the method's code. This is achieved through **dynamic proxying**, where the runtime generates proxy classes that wrap the original class. |
||||
|
|
||||
|
When a method is called on a proxied object: |
||||
|
1. The call is intercepted by the proxy |
||||
|
2. Custom behaviors (like logging, validation, or authorization) can be executed |
||||
|
3. The original method is called |
||||
|
4. Additional logic can be executed after the method completes |
||||
|
|
||||
|
This enables **cross-cutting concerns** (logic that applies across many parts of an application) to be handled in a clean, reusable way without code duplication. |
||||
|
|
||||
|
## Similarities and Differences with MVC Action/Page Filters |
||||
|
|
||||
|
If you are familiar with ASP.NET Core MVC, you've likely used **action filters** or **page filters**. Interceptors are conceptually similar but have some key differences: |
||||
|
|
||||
|
### Similarities |
||||
|
|
||||
|
* Both allow executing code before and after method execution |
||||
|
* Both are used to implement cross-cutting concerns like validation, logging, caching, or exception handling |
||||
|
* Both support asynchronous operations |
||||
|
|
||||
|
### Differences |
||||
|
|
||||
|
* **Scope**: Filters are tied to MVC's request pipeline, while interceptors can be applied to **any class or service** in the application |
||||
|
* **Configuration**: Filters are configured via attributes or middleware in MVC, while interceptors in ABP are applied through **dependency injection and dynamic proxies** |
||||
|
* **Target**: Interceptors can target application services, domain services, repositories, and virtually any service resolved from the IoC container—not just web controllers |
||||
|
|
||||
|
## How ABP Uses Interceptors |
||||
|
|
||||
|
ABP Framework extensively leverages interception to provide built-in features without requiring boilerplate code. Here are some key examples: |
||||
|
|
||||
|
### [Unit of Work (UOW)](../architecture/domain-driven-design/unit-of-work.md) |
||||
|
|
||||
|
Automatically begins and commits/rolls back a database transaction when entering or exiting an application service method. This ensures data consistency without manual transaction management. |
||||
|
|
||||
|
### [Input Validation](../fundamentals/validation.md) |
||||
|
|
||||
|
Input DTOs are automatically validated against data annotation attributes and custom validation rules before executing the service logic, providing consistent validation behavior across all services. |
||||
|
|
||||
|
### [Authorization](../fundamentals/authorization.md) |
||||
|
|
||||
|
Checks user permissions before allowing the execution of application service methods, ensuring security policies are enforced consistently. |
||||
|
|
||||
|
### [Feature](./features.md) & [Global Feature](./global-features.md) Checking |
||||
|
|
||||
|
Checks if a feature is enabled before executing the service logic, allowing you to conditionally enable or restrict functionality for tenants or the application. |
||||
|
|
||||
|
### [Auditing](./audit-logging.md) |
||||
|
|
||||
|
Automatically logs who performed an action, when it happened, what parameters were used, and what data was involved, providing comprehensive audit trails. |
||||
|
|
||||
|
## Building Your Own Interceptor |
||||
|
|
||||
|
You can create custom interceptors in ABP to implement your own cross-cutting concerns. |
||||
|
|
||||
|
### Creating an Interceptor |
||||
|
|
||||
|
Create a class that inherits from `AbpInterceptor`: |
||||
|
|
||||
|
````csharp |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Aspects; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.DynamicProxy; |
||||
|
|
||||
|
public class ExecutionTimeLogInterceptor : AbpInterceptor, ITransientDependency |
||||
|
{ |
||||
|
private readonly ILogger<ExecutionTimeLogInterceptor> _logger; |
||||
|
|
||||
|
public ExecutionTimeLogInterceptor(ILogger<ExecutionTimeLogInterceptor> logger) |
||||
|
{ |
||||
|
_logger = logger; |
||||
|
} |
||||
|
|
||||
|
public override async Task InterceptAsync(IAbpMethodInvocation invocation) |
||||
|
{ |
||||
|
var sw = Stopwatch.StartNew(); |
||||
|
|
||||
|
_logger.LogInformation("Executing {invocation.TargetObject.GetType().Name}.{invocation.Method.Name}"); |
||||
|
|
||||
|
// Proceed to the actual target method |
||||
|
await invocation.ProceedAsync(); |
||||
|
|
||||
|
sw.Stop(); |
||||
|
|
||||
|
_logger.LogInformation("Executed {invocation.TargetObject.GetType().Name}.{invocation.Method.Name} in {sw.ElapsedMilliseconds} ms"); |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
### Register Interceptors |
||||
|
|
||||
|
Create a static class that contains the `RegisterIfNeeded` method and register the interceptor in the `PreConfigureServices` method of your module. |
||||
|
|
||||
|
The `ShouldIntercept` method is used to determine if the interceptor should be registered for the given type. You can add an `IExecutionTimeLogEnabled` interface and implement it in the classes that you want to intercept. |
||||
|
|
||||
|
> `DynamicProxyIgnoreTypes` is static class that contains the types that should be ignored by the interceptor. See [Performance Considerations](#performance-considerations) for more information. |
||||
|
|
||||
|
````csharp |
||||
|
// Define an interface to mark the classes that should be intercepted |
||||
|
public interface IExecutionTimeLogEnabled |
||||
|
{ |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
````csharp |
||||
|
using System; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.DynamicProxy; |
||||
|
|
||||
|
public static class ExecutionTimeLogInterceptorRegistrar |
||||
|
{ |
||||
|
public static void RegisterIfNeeded(IOnServiceRegistredContext context) |
||||
|
{ |
||||
|
if (ShouldIntercept(context.ImplementationType)) |
||||
|
{ |
||||
|
context.Interceptors.TryAdd<ExecutionTimeLogInterceptor>(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static bool ShouldIntercept(Type type) |
||||
|
{ |
||||
|
return !DynamicProxyIgnoreTypes.Contains(type) && typeof(IExecutionTimeLogEnabled).IsAssignableFrom(type); |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
````csharp |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.OnRegistered(ExecutionTimeLogInterceptor.RegisterIfNeeded); |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
## Restrictions and Important Notes |
||||
|
|
||||
|
### Virtual Methods Requirement |
||||
|
|
||||
|
For **class proxies**, methods need to be marked as `virtual` so that they can be overridden by the proxy. Otherwise, interception will not occur. |
||||
|
|
||||
|
````csharp |
||||
|
public class MyService : ITransientDependency |
||||
|
{ |
||||
|
// This method CANNOT be intercepted (not virtual) |
||||
|
public void CannotBeIntercepted() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
// This method CAN be intercepted (virtual) |
||||
|
public virtual void CanBeIntercepted() |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
> This restriction does **not** apply to interface-based proxies. If your service implements an interface and is injected via that interface, all methods can be intercepted regardless of the `virtual` keyword. |
||||
|
|
||||
|
### Dependency Injection Scope |
||||
|
|
||||
|
Interceptors only work when services are resolved from the dependency injection container. Direct instantiation with `new` bypasses interception: |
||||
|
|
||||
|
````csharp |
||||
|
// This will NOT be intercepted |
||||
|
var service = new MyService(); |
||||
|
service.CannotBeIntercepted(); |
||||
|
|
||||
|
// This WILL be intercepted (if MyService is registered with DI) |
||||
|
var service = serviceProvider.GetService<MyService>(); |
||||
|
service.CanBeIntercepted(); |
||||
|
```` |
||||
|
|
||||
|
### Performance Considerations |
||||
|
|
||||
|
Interceptors are generally efficient, but each one adds method-call overhead. Keep the number of interceptors minimal on hot paths. |
||||
|
|
||||
|
Castle DynamicProxy can negatively impact performance for certain components, notably ASP.NET Core MVC controllers. See the discussions in [castleproject/Core#486](https://github.com/castleproject/Core/issues/486) and [abpframework/abp#3180](https://github.com/abpframework/abp/issues/3180). |
||||
|
|
||||
|
ABP uses interceptors for features like UOW, auditing, and authorization, which rely on dynamic proxy classes. For controllers, prefer implementing cross-cutting concerns with middleware or MVC/Page filters instead of dynamic proxies. |
||||
|
|
||||
|
To avoid generating dynamic proxies for specific types, use the static class `DynamicProxyIgnoreTypes` and add the base classes of the types to the list. Subclasses of any listed base class are also ignored. ABP framework already adds some base classes to the list (`ComponentBase, ControllerBase, PageModel, ViewComponent`); you can add more base classes if needed. |
||||
|
|
||||
|
> Always use interface-based proxies instead of class-based proxies for better performance. |
||||
|
|
||||
|
## See Also |
||||
|
|
||||
|
* [Video tutorial: Interceptors in ABP Framework](https://abp.io/video-courses/essentials/interception) |
||||
|
* [Castle DynamicProxy](https://www.castleproject.org/projects/dynamicproxy/) |
||||
|
* [Castle.Core.AsyncInterceptor](https://github.com/JSkimming/Castle.Core.AsyncInterceptors) |
||||
|
* [ASP.NET Core Filters](https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters) |
||||
Loading…
Reference in new issue