From 7721e6358915817791199a42ec4e06f2dd6a233f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuzhan=20A=C4=9F=C4=B1r?= <90545225+oguzhanagir@users.noreply.github.com> Date: Fri, 2 Feb 2024 14:41:16 +0300 Subject: [PATCH 1/2] updated interceptors codes. --- .../index.html | 147 ++++++++++-------- 1 file changed, 78 insertions(+), 69 deletions(-) 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..aec1018ced 100644 --- a/doc/WebSite/Articles/Aspect-Oriented-Programming-using-Interceptors/index.html +++ b/doc/WebSite/Articles/Aspect-Oriented-Programming-using-Interceptors/index.html @@ -172,22 +172,20 @@

Registering Interceptors

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 +196,45 @@

Registering Interceptors

{ public override void PreInitialize() { - MeasureDurationInterceptorRegistrar.Initialize(IocManager.IocContainer.Kernel); + MeasureDurationInterceptorRegistrar.Initialize(IocManager); + //... } + public override void Initialize() + { + //... + IocManager.Register(typeof(AbpAsyncDeterminationInterceptor), DependencyLifeStyle.Transient); + } + //... + }

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 Async 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,88 +243,90 @@ 

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.

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.

From 237b83d5ac8c43d0546c4d292db5b012a5d75400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuzhan=20A=C4=9F=C4=B1r?= <90545225+oguzhanagir@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:04:46 +0300 Subject: [PATCH 2/2] Update Aspect Oriented Programming using Interceptors document. --- .../index.html | 181 ++++++------------ 1 file changed, 56 insertions(+), 125 deletions(-) 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 aec1018ced..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 @@

Creating Interceptors

namespace InterceptionDemo.Interceptors { - public class MeasureDurationInterceptor : IInterceptor + public class MeasureDurationInterceptor : AbpInterceptorBase, ITransientDependency { public ILogger Logger { get; set; } @@ -142,26 +142,28 @@

Creating Interceptors

Logger = NullLogger.Instance; } - public void Intercept(IInvocation invocation) + public override void InterceptSynchronous(IInvocation invocation) { //Before method execution var stopwatch = Stopwatch.StartNew(); - + //Executing the actual method invocation.Proceed(); - + //After method execution stopwatch.Stop(); Logger.InfoFormat( - "MeasureDurationInterceptor: {0} executed in {1} milliseconds.", + "MeasureDurationInterceptor: {0} executed in {1} milliseconds.", invocation.MethodInvocationTarget.Name, - stopwatch.Elapsed.TotalMilliseconds.ToString("0.000") + stopwatch.Elapsed.TotalMilliseconds.ToString("0.000") ); } + + //... } } -

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.

@@ -176,13 +178,13 @@

Registering Interceptors

{ public static void Initialize(IIocManager iocManager) { - iocManager.IocContainer.Kernel.ComponentRegistered += (key, handler) => + iocManager.IocContainer.Kernel.ComponentRegistered += (key, handler) => { if (typeof (IApplicationService).IsAssignableFrom(handler.ComponentModel.Implementation)) { handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(AbpAsyncDeterminationInterceptor))); } - }; + }; } } @@ -307,7 +309,7 @@

Intercepting Async Methods

-

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:

@@ -331,26 +333,26 @@

Intercepting Async Methods

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; }
 
@@ -359,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 }); - } -} -

Source Code

You can get the latest source code here https://github.com/aspnetboilerplate/aspnetboilerplate-samples/tree/master/InterceptionDemo