Click here to Skip to main content
15,880,967 members
Articles / Web Development / HTML

Aspect Oriented Programming using Interceptors within Castle Windsor and ABP Framework

Rate me:
Please Sign up or sign in to vote.
5.00/5 (28 votes)
21 Feb 2018CPOL5 min read 73.3K   22   17
In this article, I'll show you how to create interceptors to implement AOP techniques. I'll use ASP.NET Boilerplate (ABP) as base application framework and Castle Windsor for the interception library.

Contents

Introduction

In this article, I'll show you how to create interceptors to implement AOP techniques. I'll use ASP.NET Boilerplate (ABP) as base application framework and Castle Windsor for the interception library. Most of the techniques described here are also valid for using Castle Windsor independent from ABP framework.

What is Aspect Oriented Programming (AOP) and Method Interception?

Wikipedia: "In computing, aspect-oriented programming (AOP) is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns. It does so by adding additional behavior to existing code (an advice) without modifying the code itself, instead separately specifying which code is modified via a "pointcut" specification".

In an application, we may have some repeating/similar code for logging, authorization, validation, exception handling and so on...

Manual Way (Without AOP)

An example code does all manually:

C#
public class TaskAppService : ApplicationService
{
    private readonly IRepository<Task> _taskRepository;
    private readonly IPermissionChecker _permissionChecker;
    private readonly ILogger _logger;

    public TaskAppService(IRepository<Task> taskRepository, 
		IPermissionChecker permissionChecker, ILogger logger)
    {
        _taskRepository = taskRepository;
        _permissionChecker = permissionChecker;
        _logger = logger;
    }

    public void CreateTask(CreateTaskInput input)
    {
        _logger.Debug("Running CreateTask method: " + input.ToJsonString());

        try
        {
            if (input == null)
            {
                throw new ArgumentNullException("input");
            }

            if (!_permissionChecker.IsGranted("TaskCreationPermission"))
            {
                throw new Exception("No permission for this operation!");
            }

            _taskRepository.Insert(new Task(input.Title, input.Description, input.AssignedUserId));
        }
        catch (Exception ex)
        {
            _logger.Error(ex.Message, ex);
            throw;
        }

        _logger.Debug("CreateTask method is successfully completed!");
    }
}

In CreateTask method, the essential code is _taskRepository.Insert(...) method call. All other code is repeating code and will be the same/similar for our other methods of TaskAppService. In a real application, we will have many application service need the same functionality. Also, we may have other similar code for database connection open and close, audit logging and so on...

AOP Way

If we use AOP and interception techniques, TaskAppService could be written as shown below with the same functionality:

C#
public class TaskAppService : ApplicationService
{
    private readonly IRepository<Task> _taskRepository;

    public TaskAppService(IRepository<Task> taskRepository)
    {
        _taskRepository = taskRepository;
    }

    [AbpAuthorize("TaskCreationPermission")]
    public void CreateTask(CreateTaskInput input)
    {
        _taskRepository.Insert(new Task(input.Title, input.Description, input.AssignedUserId));
    }
}

Now, it exactly does what is unique to CreateTask method. Exception handling, validation and logging code are completely removed since they are similar for other methods and can be centralized conventionally. Authorization code is replaced with AbpAuthorize attribute which is simpler to write and read.

Fortunately, all these and much more are automatically done by ABP framework. But, you may want to create some custom interception logic that is specific to your own application requirements. That's why I created this article.

About the Sample Project

I created a sample project from ABP startup templates (including module zero) and added to a Github repository.

Creating Interceptors

Let's begin with a simple interceptor that measures the execution duration of a method:

C#
using System.Diagnostics;
using Castle.Core.Logging;
using Castle.DynamicProxy;

namespace InterceptionDemo.Interceptors
{
    public class MeasureDurationInterceptor : IInterceptor
    {
        public ILogger Logger { get; set; }

        public MeasureDurationInterceptor()
        {
            Logger = NullLogger.Instance;
        }

        public void Intercept(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.",
                invocation.MethodInvocationTarget.Name,
                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 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.

Registering Interceptors

After we create an interceptor, we can register it for desired classes. For example, we may want to register MeasureDurationInterceptor for all methods of all application service classes. We can easily identify application service classes since all application service classes implement IApplicationService in ABP framework.

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:

C#
public static class MeasureDurationInterceptorRegistrar
{
    public static void Initialize(IKernel kernel)
    {
        kernel.ComponentRegistered += Kernel_ComponentRegistered;
    }

    private static void Kernel_ComponentRegistered(string key, IHandler handler)
    {
        if (typeof (IApplicationService).IsAssignableFrom(handler.ComponentModel.Implementation))
        {
            handler.ComponentModel.Interceptors.Add
            (new InterceptorReference(typeof(MeasureDurationInterceptor)));
        }
    }
}

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.

After creating such a registration code, we need to call the Initialize method from somewhere else. It's best to call it in PreInitialize event of your module (since classes are registered to IOC generally in Initialize step):

C#
public class InterceptionDemoApplicationModule : AbpModule
{
    public override void PreInitialize()
    {
        MeasureDurationInterceptorRegistrar.Initialize(IocManager.IocContainer.Kernel);
    }

    //...
}

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.

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).

Let's change MeasureDurationInterceptor to support async methods, then explain how we implemented it:

C#
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 ILogger Logger { get; set; }

        public MeasureDurationAsyncInterceptor()
        {
            Logger = NullLogger.Instance;
        }

        public void Intercept(IInvocation invocation)
        {
            if (IsAsyncMethod(invocation.Method))
            {
                InterceptAsync(invocation);
            }
            else
            {
                InterceptSync(invocation);
            }
        }

        private void InterceptAsync(IInvocation invocation)
        {
            //Before method execution
            var stopwatch = Stopwatch.StartNew();

            //Calling the actual method, but execution has not been finished yet
            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")
                        );
                });
        }

        private void InterceptSync(IInvocation invocation)
        {
            //Before method execution
            var stopwatch = Stopwatch.StartNew();

            //Executing the actual method
            invocation.Proceed();

            //After method execution
            stopwatch.Stop();
            Logger.InfoFormat(
                "MeasureDurationAsyncInterceptor: {0} executed in {1} milliseconds.",
                invocation.MethodInvocationTarget.Name,
                stopwatch.Elapsed.TotalMilliseconds.ToString("0.000")
                );
        }
        
        public static bool IsAsyncMethod(MethodInfo method)
        {
            return (
                method.ReturnType == typeof(Task) ||
                (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
                );
        }
    }
}

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:

C#
public static class MeasureDurationInterceptorRegistrar
{
    public static void Initialize(IKernel kernel)
    {
        kernel.ComponentRegistered += Kernel_ComponentRegistered;
    }

    private static void Kernel_ComponentRegistered(string key, IHandler handler)
    {
        if (typeof(IApplicationService).IsAssignableFrom(handler.ComponentModel.Implementation))
        {
            handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(MeasureDurationInterceptor)));
            handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(MeasureDurationAsyncInterceptor)));
        }
    }
}

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.

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.

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:

C#
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 ILogger Logger { get; set; }

        public MeasureDurationWithPostAsyncActionInterceptor()
        {
            Logger = NullLogger.Instance;
        }

        public void Intercept(IInvocation invocation)
        {
            if (IsAsyncMethod(invocation.Method))
            {
                InterceptAsync(invocation);
            }
            else
            {
                InterceptSync(invocation);
            }
        }

        private void InterceptAsync(IInvocation invocation)
        {
            //Before method execution
            var stopwatch = Stopwatch.StartNew();

            //Calling the actual method, but execution has not been finished yet
            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);
                    });
            }
        }

        private void InterceptSync(IInvocation invocation)
        {
            //Before method execution
            var stopwatch = Stopwatch.StartNew();

            //Executing the actual method
            invocation.Proceed();

            //After method execution
            LogExecutionTime(invocation, stopwatch);
        }

        public static bool IsAsyncMethod(MethodInfo method)
        {
            return (
                method.ReturnType == typeof(Task) ||
                (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
                );
        }

        private async Task TestActionAsync(IInvocation invocation)
        {
            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);
        }

        private void LogExecutionTime(IInvocation invocation, Stopwatch stopwatch)
        {
            stopwatch.Stop();
            Logger.InfoFormat(
                "MeasureDurationWithPostAsyncActionInterceptor: {0} executed in {1} milliseconds.",
                invocation.MethodInvocationTarget.Name,
                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:

C#
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

Article History

  • 2018-02-22
    • Upgraded source code to ABP v3.4.
  • 2017-06-28
    • Upgraded source code to ABP v2.1.3.
    • Updated links in the article.
  • 2016-07-20
    • Upgraded source code to ABP v0.10.
    • Updated article upon changes.
  • 2016-03-01
    • Added async method interception sample.
  • 2016-02-23
    • Initial publication.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Founder Volosoft
Turkey Turkey
I have started programming at 14 years old using Pascal as hobby. Then I interested in web development (HTML, JavaScript, ASP...) before university.

I graduated from Sakarya University Computer Engineering. At university, I learned C++, Visual Basic.NET, C#, ASP.NET and Java. I partly implemented ARP, IP and TCP protocols in Java as my final term project.

Now, I am working on Windows and web based software development mostly using Microsoft technologies in my own company.

My open source projects:

* ABP Framework: https://abp.io
* jTable: http://jtable.org
* Others: https://github.com/hikalkan

My personal web site:

https://halilibrahimkalkan.com

Comments and Discussions

 
QuestionHow to handle exception Pin
pm66621-Nov-19 22:09
pm66621-Nov-19 22:09 
AnswerRe: How to handle exception Pin
pm66622-Nov-19 2:44
pm66622-Nov-19 2:44 
QuestionHow to take the return value for a Task<T> Pin
Gianpiero Caretti6-Apr-18 5:26
Gianpiero Caretti6-Apr-18 5:26 
AnswerRe: How to take the return value for a Task<T> Pin
Gianpiero Caretti6-Apr-18 13:00
Gianpiero Caretti6-Apr-18 13:00 
BugBug if combine with other interceptor and exception thrown Pin
Van Ly26-Apr-17 22:14
Van Ly26-Apr-17 22:14 
GeneralRe: Bug if combine with other interceptor and exception thrown Pin
Halil ibrahim Kalkan28-Jun-17 0:41
Halil ibrahim Kalkan28-Jun-17 0:41 
Resolved.

GeneralMy vote of 5 Pin
JoshYates198029-Oct-16 16:57
professionalJoshYates198029-Oct-16 16:57 
GeneralRe: My vote of 5 Pin
Halil ibrahim Kalkan28-Jun-17 0:42
Halil ibrahim Kalkan28-Jun-17 0:42 
QuestionGood article Pin
Olcay Seker19-Jul-16 23:10
Olcay Seker19-Jul-16 23:10 
AnswerRe: Good article Pin
Halil ibrahim Kalkan20-Jul-16 8:02
Halil ibrahim Kalkan20-Jul-16 8:02 
Questionone question Pin
fengzijun19-Jul-16 21:00
fengzijun19-Jul-16 21:00 
AnswerRe: one question Pin
Halil ibrahim Kalkan20-Jul-16 8:06
Halil ibrahim Kalkan20-Jul-16 8:06 
GeneralRe: one question Pin
fengzijun20-Jul-16 15:22
fengzijun20-Jul-16 15:22 
QuestionInterceptors for Async Methods Pin
Partha Sarathi Maharatha23-Feb-16 12:36
Partha Sarathi Maharatha23-Feb-16 12:36 
AnswerRe: Interceptors for Async Methods Pin
Halil ibrahim Kalkan23-Feb-16 20:23
Halil ibrahim Kalkan23-Feb-16 20:23 
GeneralMy vote of 5 Pin
AlperOnline23-Feb-16 3:00
AlperOnline23-Feb-16 3:00 
GeneralRe: My vote of 5 Pin
Halil ibrahim Kalkan23-Feb-16 5:08
Halil ibrahim Kalkan23-Feb-16 5:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.