diff --git a/doc/WebSite/Articles/Aspect-Oriented-Programming-using-Interceptors/index.html b/doc/WebSite/Articles/Aspect-Oriented-Programming-using-Interceptors/index.html index bc2d919325..6b28f08604 100644 --- a/doc/WebSite/Articles/Aspect-Oriented-Programming-using-Interceptors/index.html +++ b/doc/WebSite/Articles/Aspect-Oriented-Programming-using-Interceptors/index.html @@ -133,7 +133,7 @@
An interceptor is a class that implements IInterceptor
interface (of Castle Windsor). It defines the Intercept
method which gets an IInvocation
argument. With this invocation argument, we can investigate the executing method, method arguments, return value, method's declared class, assembly and much more. Intercept
method is called whenever a registered method is called (see registration section below). Proceed()
method executes the actual intercepted method. We can write code before and after the actual method execution, as shown in this example.
An interceptor is a class that implements AbpInterceptorBase
abstract class (of ABP). It defines the InterceptSynchronous
method which gets an IInvocation
argument. With this invocation argument, we can investigate the executing method, method arguments, return value, method's declared class, assembly and much more. InterceptSynchronous
method is called whenever a registered method is called (see registration section below). Proceed()
method executes the actual intercepted method. We can write code before and after the actual method execution, as shown in this example.
An Interceptor
class can also inject its dependencies like other classes. In this example, we property-injected an ILogger
to write method execution duration to the log.
There are some alternative ways of registering interceptors. But, it's the most proper way in ABP to handle ComponentRegistered
event of Castle Windsors Kernel
:
-public static class MeasureDurationInterceptorRegistrar
-{
- public static void Initialize(IKernel kernel)
+ internal static class MeasureDurationInterceptorRegistrar
{
- kernel.ComponentRegistered += Kernel_ComponentRegistered;
- }
-
- private static void Kernel_ComponentRegistered(string key, IHandler handler)
- {
- if (typeof (IApplicationService).IsAssignableFrom(handler.ComponentModel.Implementation))
+ public static void Initialize(IIocManager iocManager)
{
- handler.ComponentModel.Interceptors.Add
- (new InterceptorReference(typeof(MeasureDurationInterceptor)));
- }
+ iocManager.IocContainer.Kernel.ComponentRegistered += (key, handler) =>
+ {
+ if (typeof (IApplicationService).IsAssignableFrom(handler.ComponentModel.Implementation))
+ {
+ handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(AbpAsyncDeterminationInterceptor)));
+ }
+ };
+ }
}
-}
+
In this way, whenever a class is registered to dependency injection system (IOC), we can handle the event, check if this class is one of those classes we want to intercept and add interceptor if so.
@@ -198,36 +198,45 @@After these steps, I run and login to the application. Then, I check log file and see logs:
-INFO 2016-02-23 14:59:28,611 [63 ] .Interceptors.MeasureDurationInterceptor - -GetCurrentLoginInformations executed in 4,939 milliseconds.+INFO 2024-02-01 10:28:07,657 [orker] .Interceptors.MeasureDurationInterceptor - MeasureDurationInterceptor: +GetCurrentLoginInformations executed in 3.633 milliseconds.
Note: GetCurrentLoginInformations is a method of SessionAppService class. You can check it in source code, but it's not important since our interceptor does not know details of intercepted methods.
Intercepting an async method is different than intercepting a sync method. For example, MeasureDurationInterceptor
defined above does not work properly for async methods. Because, an async method immediately returns a Task
and it's executed asynchronously. So, we can not measure when it's actually completed (Actually, the example GetCurrentLoginInformations
above was also an async method and 4,939 ms was a wrong value).
Intercepting an async method is different than intercepting a sync method. For example, MeasureDurationInterceptor
defined above does not work properly for async methods. Because, an async method immediately returns a Task
and it's executed asynchronously. So, we can not measure when it's actually completed (Actually, the example GetCurrentLoginInformations
above was also an async method and 3,633 ms was a wrong value).
Let's change MeasureDurationInterceptor to support async methods, then explain how we implemented it:
+using Abp.Dependency;
+using Castle.Core.Logging;
+using Castle.DynamicProxy;
using System.Diagnostics;
using System.Reflection;
using System.Threading.Tasks;
-using Castle.Core.Logging;
-using Castle.DynamicProxy;
namespace InterceptionDemo.Interceptors
{
- public class MeasureDurationAsyncInterceptor : IInterceptor
+ public class MeasureDurationAsyncInterceptor : AbpInterceptorBase, ITransientDependency
{
public ILogger Logger { get; set; }
@@ -236,112 +245,114 @@ Intercepting Async Methods
Logger = NullLogger.Instance;
}
- public void Intercept(IInvocation invocation)
- {
- if (IsAsyncMethod(invocation.Method))
- {
- InterceptAsync(invocation);
- }
- else
- {
- InterceptSync(invocation);
- }
- }
-
- private void InterceptAsync(IInvocation invocation)
+ public override void InterceptSynchronous(IInvocation invocation)
{
//Before method execution
var stopwatch = Stopwatch.StartNew();
- //Calling the actual method, but execution has not been finished yet
+ //Executing the actual method
invocation.Proceed();
- //We should wait for finishing of the method execution
- ((Task) invocation.ReturnValue)
- .ContinueWith(task =>
- {
- //After method execution
- stopwatch.Stop();
- Logger.InfoFormat(
- "MeasureDurationAsyncInterceptor: {0} executed in {1} milliseconds.",
- invocation.MethodInvocationTarget.Name,
- stopwatch.Elapsed.TotalMilliseconds.ToString("0.000")
- );
- });
+ //After method execution
+ stopwatch.Stop();
+ Logger.InfoFormat(
+ "MeasureDurationAsyncInterceptor: {0} executed in {1} milliseconds.",
+ invocation.MethodInvocationTarget.Name,
+ stopwatch.Elapsed.TotalMilliseconds.ToString("0.000")
+ );
}
- private void InterceptSync(IInvocation invocation)
+ protected override async Task InternalInterceptAsynchronous(IInvocation invocation)
{
+ var proceedInfo = invocation.CaptureProceedInfo();
+
//Before method execution
var stopwatch = Stopwatch.StartNew();
- //Executing the actual method
- invocation.Proceed();
+ proceedInfo.Invoke();
+ var task = (Task)invocation.ReturnValue;
+ await task;
//After method execution
stopwatch.Stop();
Logger.InfoFormat(
- "MeasureDurationAsyncInterceptor: {0} executed in {1} milliseconds.",
+ "MeasureDurationAsyncInterceptor: {0} executed in {1} milliseconds.",
invocation.MethodInvocationTarget.Name,
- stopwatch.Elapsed.TotalMilliseconds.ToString("0.000")
+ stopwatch.Elapsed.TotalMilliseconds.ToString("0.000")
);
}
-
- public static bool IsAsyncMethod(MethodInfo method)
+
+ protected override async Task InternalInterceptAsynchronous(IInvocation invocation)
{
- return (
- method.ReturnType == typeof(Task) ||
- (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
+ var proceedInfo = invocation.CaptureProceedInfo();
+
+ //Before method execution
+ var stopwatch = Stopwatch.StartNew();
+
+ proceedInfo.Invoke();
+
+ var taskResult = (Task)invocation.ReturnValue;
+ var result = await taskResult;
+
+ //After method execution
+ stopwatch.Stop();
+ Logger.InfoFormat(
+ "MeasureDurationAsyncInterceptor: {0} executed in {1} milliseconds.",
+ invocation.MethodInvocationTarget.Name,
+ stopwatch.Elapsed.TotalMilliseconds.ToString("0.000")
);
+
+ return result;
}
}
-}
+}
+
+
-Since sync and async execution logic is completely different, I checked if current method is async or sync (IsAsyncMethod
does it). I moved previous code to InterceptSync
method and introduced new InterceptAsync
method. I used Task.ContinueWith(...)
method to perform action after task complete. ContinueWith
method works even if intercepted method throws exception.
The method within the AbpAsyncDeterminationInterceptor
is used to determine whether a method is asynchronous or not. This interceptor checks whether methods are asynchronous or not. I moved previous code to InterceptSync
method and introduced new InternalInterceptAsynchronous
method.
Now, I'm registering MeasureDurationAsyncInterceptor
as a second interceptor for application services by modifying MeasureDurationInterceptorRegistrar
defined above:
-public static class MeasureDurationInterceptorRegistrar
+internal static class MeasureDurationInterceptorRegistrar
{
- public static void Initialize(IKernel kernel)
+ public static void Initialize(IIocManager iocManager)
{
- kernel.ComponentRegistered += Kernel_ComponentRegistered;
- }
-
- private static void Kernel_ComponentRegistered(string key, IHandler handler)
- {
- if (typeof(IApplicationService).IsAssignableFrom(handler.ComponentModel.Implementation))
+ iocManager.IocContainer.Kernel.ComponentRegistered += (key, handler) =>
{
- handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(MeasureDurationInterceptor)));
- handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(MeasureDurationAsyncInterceptor)));
- }
+ if (typeof (IApplicationService).IsAssignableFrom(handler.ComponentModel.Implementation))
+ {
+ handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(AbpAsyncDeterminationInterceptor)));
+ handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(AbpAsyncDeterminationInterceptor)));
+ }
+ };
}
-}
+}
+
If we run the application again, we will see that MeasureDurationAsyncInterceptor
measured much more longer than MeasureDurationInterceptor
, since it actually waits until method completely executed.
-INFO 2016-03-01 10:29:07,592 [10 ] .Interceptors.MeasureDurationInterceptor - MeasureDurationInterceptor: GetCurrentLoginInformations executed in 4.964 milliseconds. -INFO 2016-03-01 10:29:07,693 [7 ] rceptors.MeasureDurationAsyncInterceptor - MeasureDurationAsyncInterceptor: GetCurrentLoginInformations executed in 104,994 milliseconds. +INFO 2024-02-01 09:11:15,467 [orker] .Interceptors.MeasureDurationInterceptor - MeasureDurationInterceptor: GetCurrentLoginInformations executed in 2.010 milliseconds. +INFO 2024-02-01 09:11:15,563 [orker] rceptors.MeasureDurationAsyncInterceptor - MeasureDurationAsyncInterceptor: GetCurrentLoginInformations executed in 1.201 milliseconds.-
This way, we can properly intercept async methods to run code before and after. But, if our before and after code involve another async method calls, things get a bit complicated.
+This way, we can properly intercept async methods to run code before and after.
-First of all, I could not find a way of executing async code before invocation.Proceed()
. Because Castle Windsor does not support async naturally (other IOC managers also don't support as I know). So, if you need to run code before the actual method execution, do it synchronously. If you find a way of it, please share your solution as comment to this article.
We can execute async code after method execution. I changed InterceptAsync
like that to support it:
We can execute async code after method execution. I changed InternalInterceptAsynchronous
like that to support it:
+using Abp.Dependency;
+using Castle.Core.Logging;
+using Castle.DynamicProxy;
+using System;
using System.Diagnostics;
using System.Reflection;
using System.Threading.Tasks;
-using Castle.Core.Logging;
-using Castle.DynamicProxy;
namespace InterceptionDemo.Interceptors
{
- public class MeasureDurationWithPostAsyncActionInterceptor : IInterceptor
+ public class MeasureDurationWithPostAsyncActionInterceptor : AbpInterceptorBase, ITransientDependency
{
public ILogger Logger { get; set; }
@@ -350,144 +361,73 @@ Intercepting Async Methods
Logger = NullLogger.Instance;
}
- public void Intercept(IInvocation invocation)
- {
- if (IsAsyncMethod(invocation.Method))
- {
- InterceptAsync(invocation);
- }
- else
- {
- InterceptSync(invocation);
- }
- }
-
- private void InterceptAsync(IInvocation invocation)
+ public override void InterceptSynchronous(IInvocation invocation)
{
//Before method execution
var stopwatch = Stopwatch.StartNew();
- //Calling the actual method, but execution has not been finished yet
+ //Executing the actual method
invocation.Proceed();
- //Wait task execution and modify return value
- if (invocation.Method.ReturnType == typeof(Task))
- {
- invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithPostActionAndFinally(
- (Task) invocation.ReturnValue,
- async () => await TestActionAsync(invocation),
- ex =>
- {
- LogExecutionTime(invocation, stopwatch);
- });
- }
- else //Task<TResult>
- {
- invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithPostActionAndFinallyAndGetResult(
- invocation.Method.ReturnType.GenericTypeArguments[0],
- invocation.ReturnValue,
- async () => await TestActionAsync(invocation),
- ex =>
- {
- LogExecutionTime(invocation, stopwatch);
- });
- }
+ //After method execution
+ LogExecutionTime(invocation, stopwatch);
}
- private void InterceptSync(IInvocation invocation)
+ protected override async Task InternalInterceptAsynchronous(IInvocation invocation)
{
+ var proceedInfo = invocation.CaptureProceedInfo();
+
//Before method execution
var stopwatch = Stopwatch.StartNew();
- //Executing the actual method
- invocation.Proceed();
+ proceedInfo.Invoke();
+ var task = (Task)invocation.ReturnValue;
+ await TestActionAsync(invocation.MethodInvocationTarget.Name);
+ await task;
//After method execution
LogExecutionTime(invocation, stopwatch);
}
- public static bool IsAsyncMethod(MethodInfo method)
+ protected override async Task InternalInterceptAsynchronous(IInvocation invocation)
{
- return (
- method.ReturnType == typeof(Task) ||
- (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
- );
+ var proceedInfo = invocation.CaptureProceedInfo();
+
+ //Before method execution
+ var stopwatch = Stopwatch.StartNew();
+
+ proceedInfo.Invoke();
+
+ var taskResult = (Task)invocation.ReturnValue;
+ await TestActionAsync(invocation.MethodInvocationTarget.Name);
+ var result = await taskResult;
+
+ //After method execution
+ LogExecutionTime(invocation, stopwatch);
+
+ return result;
}
- private async Task TestActionAsync(IInvocation invocation)
+ private async Task TestActionAsync(string methodName)
{
- Logger.Info("Waiting after method execution for " + invocation.MethodInvocationTarget.Name);
- await Task.Delay(200); //Here, we can await another methods. This is just for test.
- Logger.Info("Waited after method execution for " + invocation.MethodInvocationTarget.Name);
+ Logger.Info($"Waiting after method execution for {methodName}");
+ await Task.Delay(200); // Here, we can await other methods. This is just for test.
+ Logger.Info($"Waited after method execution for {methodName}");
}
private void LogExecutionTime(IInvocation invocation, Stopwatch stopwatch)
{
stopwatch.Stop();
Logger.InfoFormat(
- "MeasureDurationWithPostAsyncActionInterceptor: {0} executed in {1} milliseconds.",
+ "MeasureDurationWithPostAsyncActionInterceptor: {0} executed in {1} milliseconds.",
invocation.MethodInvocationTarget.Name,
- stopwatch.Elapsed.TotalMilliseconds.ToString("0.000")
+ stopwatch.Elapsed.TotalMilliseconds.ToString("0.000")
);
}
}
-}
-
-If we want to execute an async method after method execution, we should replace the return value with the second method's return value. I created a magical InternalAsyncHelper
class to accomplish it. InternalAsyncHelper
is shown below:
-internal static class InternalAsyncHelper
-{
- public static async Task AwaitTaskWithPostActionAndFinally(Task actualReturnValue, Func<Task> postAction, Action<Exception> finalAction)
- {
- Exception exception = null;
-
- try
- {
- await actualReturnValue;
- await postAction();
- }
- catch (Exception ex)
- {
- exception = ex;
- throw;
- }
- finally
- {
- finalAction(exception);
- }
- }
-
- public static async Task<T> AwaitTaskWithPostActionAndFinallyAndGetResult<T>(Task<T> actualReturnValue, Func<Task> postAction, Action<Exception> finalAction)
- {
- Exception exception = null;
-
- try
- {
- var result = await actualReturnValue;
- await postAction();
- return result;
- }
- catch (Exception ex)
- {
- exception = ex;
- throw;
- }
- finally
- {
- finalAction(exception);
- }
- }
+}
+
- public static object CallAwaitTaskWithPostActionAndFinallyAndGetResult(Type taskReturnType, object actualReturnValue, Func<Task> action, Action<Exception> finalAction)
- {
- return typeof (InternalAsyncHelper)
- .GetMethod("AwaitTaskWithPostActionAndFinallyAndGetResult", BindingFlags.Public | BindingFlags.Static)
- .MakeGenericMethod(taskReturnType)
- .Invoke(null, new object[] { actualReturnValue, action, finalAction });
- }
-}
-
You can get the latest source code here https://github.com/aspnetboilerplate/aspnetboilerplate-samples/tree/master/InterceptionDemo