Aspect Oriented Programming using Interceptors within Castle Windsor and ABP Framework AOP

http://www.codeproject.com/Articles/1080517/Aspect-Oriented-Programming-using-Interceptors-wit

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 toincrease 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:

Hide   Shrink    Copy Code

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:

Hide   Copy Code

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 handlingvalidation 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:

Hide   Shrink    Copy Code

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

namespace InterceptionDemo.Interceptors
{
    public class MeasureDurationInterceptor : IInterceptor
    {
        private readonly ILogger _logger;

        public MeasureDurationInterceptor(ILogger logger)
        {
            _logger = logger;
        }

        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(
                "{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 Interceptmethod 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 constructor-injected an ILogger to write method execution duration to the log.

Registering Interceptors

After we created an interceptor, we can register it for desired classes. For example, we may want to registerMeasureDurationInterceptor 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 most proper way in ABP to handleComponentRegistered event of Castle Windsors Kernel:

Hide   Copy Code

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 Initializestep):

Hide   Copy Code

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:

Hide   Copy Code

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:

Hide   Shrink    Copy Code

public class MeasureDurationAsyncInterceptor : IInterceptor
{
    private readonly ILogger _logger;

    public MeasureDurationAsyncInterceptor(ILogger logger)
    {
        _logger = logger;
    }

    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(
            "{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 newInterceptAsync 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:

Hide   Copy Code

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.

Hide   Copy Code

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:

Hide   Shrink    Copy Code

public class MeasureDurationWithPostAsyncActionInterceptor : IInterceptor
{
    private readonly ILogger _logger;

    public MeasureDurationWithPostAsyncActionInterceptor(ILogger logger)
    {
        _logger = logger;
    }

    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:

Hide   Shrink    Copy Code

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 });
    }
}

More

I will improve this article by adding some use cases:

  • Defining attributes to control interception logic
  • Working with method arguments
  • Manipulating return values
  • ...

While you can do all starting with the MeasureDurationInterceptor sample, follow updates of this article to get concrete examples.

Article History

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

Share

时间: 2024-11-03 03:27:47

Aspect Oriented Programming using Interceptors within Castle Windsor and ABP Framework AOP的相关文章

Java实战之03Spring-03Spring的核心之AOP(Aspect Oriented Programming 面向切面编程)

三.Spring的核心之AOP(Aspect Oriented Programming 面向切面编程) 1.AOP概念及原理 1.1.什么是AOP OOP:Object Oriented Programming面向对象编程 AOP:Aspect Oriented Programming面向切面编程 1.2.代理 充分理解:间接 主要作用:拦截被代理对象执行的方法,同时对方法进行增强. 1.2.1.静态代理 特点:代理类是一个真实存在的类.装饰者模式就是静态代理的一种体现形式. 1.2.2.动态代

Spring Aspect Oriented Programming

Spring Aspect Oriented ProgrammingSpring Aspect Oriented ProgrammingSpring Aspect Oriented ProgrammingSpring Aspect Oriented ProgrammingSpring Aspect Oriented ProgrammingSpring Aspect Oriented Programming http://www.djkk.com/blog/yvbf3319 http://www.

AOP Aspect Oriented Programming

原理AOP(Aspect Oriented Programming),也就是面向方面编程的技术.AOP基于IoC基础,是对OOP的有益补充. AOP将应用系统分为两部分,核心业务逻辑(Core business concerns)及横向的通用逻辑,也就是所谓的方面Crosscutting enterprise concerns,例如,所有大中型应用都要涉及到的持久化管理(Persistent).事务管理(Transaction Management).安全管理(Security).日志管理(Lo

Spring面向切面编程(AOP,Aspect&#160;Oriented&#160;Programming)

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程(也叫面向方面),可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术.主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等.使用JDK的动态代理可以实现AOP. AOP通过代理的方式都程序动态统一添加功能 现在要给功能4增加一些额外的行为,如处理日志,处理权限等,可以使用代理实现.我们在功能4外面包装一个对象,假设叫A, model原来是直接调用功能4,

Spring之 Aspect Oriented Programming with Spring

1. Concepts Aspect-Oriented Programming (AOP) complements OOP by providing another way of thinking about program structure. While OO decomposes applications into a hierarchy of objects, AOP decomposes programs into aspects or concerns. This enables m

xml的方式配置AOP:Aspect Oriented Programming

在某些类中, 什么时机, 做什么事情 切入点(point-cut): 在某些类中(Class<?>[] itfc = new Class<?>[] { IStudentService.class }) 通知: 什么时机, 做什么事情(InvocationHandler的invoke方法) 切面: 切入点 + 通知 织入(weaver): Proxy.newProxyInstance: 把切入点和通知施加到具体的业务逻辑上的过程 XML配置AOP步骤: 1,准备了一个实现接口的bea

关于spring.net的面向切面编程 (Aspect Oriented Programming with Spring.NET)-切入点(pointcut)API

本文翻译自Spring.NET官方文档Version 1.3.2. 受限于个人知识水平,有些地方翻译可能不准确,但是我还是希望我的这些微薄的努力能为他人提供帮助. 侵删. 让我们看看 Spring.NET 如何处理一些重要的关于切入点的概念. 一些概念 Spring.NET的切入点和通知是相互独立的,因此针对不同的通知类型可以使用相同的切入点. Spring.Aop.IPointcut 接口是最核心的,是用来将通知定位到特定的类型或者方法,接口细节如下: 1 public interface I

关于spring.net的面向切面编程 (Aspect Oriented Programming with Spring.NET)-使用工厂创建代理(Using the ProxyFactoryObject to create AOP proxies)

本文翻译自Spring.NET官方文档Version 1.3.2. 受限于个人知识水平,有些地方翻译可能不准确,但是我还是希望我的这些微薄的努力能为他人提供帮助. 侵删. 如果你正在为你的业务模型使用IoC容器--这是个好主意--你将会想使用某个 Spring.NET's AOP特定的IFactoryObject 的实现(要记住,一个工厂的实例提供了一个间接层,使这个工厂能够创建不同类型的对象-5.3.9节,"通过使用其他类型和实例创建一个对象"). 一个基本的创建Spring.NET

[Spring] Aspect Oriented Programming with Spring | AOP | 切面 | 切点

使用Spring面向切面编程 1.介绍 AOP是OOP的补充,提供了另一种关于程序结构的思路. OOP的模块化的关键单位是 类 . AOP的则是aspect切面. AOP 将程序的逻辑分成独立的块(叫做concern 重心,关注点). AOP是用来增加跨切重心(cross-cutting concerns)的模块化的. A cross-cutting concern 是一个可以影响整个程序的concern ,应该尽可能地在代码的某个位置中心化. 例如:事务管理,授权,登录,日志,安全等. 2.为