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

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

  1. 关于面向切面编程Aspect Oriented Programming(AOP)

    最近学到spring ,出来了一个新概念,面向切面编程,下面做个笔记,引自百度百科. Aspect Oriented Programming(AOP),面向切面编程,是一个比较热门的话题.AOP主要实 ...

  2. Aspect Oriented Programming

    AOP(Aspect Oriented Programming),面向切面编程(也叫面向方面)是目前软件开发中的一个热点.利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度 ...

  3. Spring面向切面编程(AOP,Aspect Oriented Programming)

    AOP为Aspect Oriented Programming的缩写,意为:面向切面编程(也叫面向方面),可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术. ...

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

    三.Spring的核心之AOP(Aspect Oriented Programming 面向切面编程) 1.AOP概念及原理 1.1.什么是AOP OOP:Object Oriented Progra ...

  5. AOP Aspect Oriented Programming

    原理AOP(Aspect Oriented Programming),也就是面向方面编程的技术.AOP基于IoC基础,是对OOP的有益补充. AOP将应用系统分为两部分,核心业务逻辑(Core bus ...

  6. 面向切面编程 ( Aspect Oriented Programming with Spring )

    Aspect Oriented Programming with Spring 1. 简介 AOP是与OOP不同的一种程序结构.在OOP编程中,模块的单位是class(类):然而,在AOP编程中模块的 ...

  7. Java 面向切面编程(Aspect Oriented Programming,AOP)

    本文内容 实例 引入 原始方法 装饰者模式 JDK 动态代理和 cglib 代理 直接使用 AOP 框架--AspectWerkz 最近跳槽了,新公司使用了 AOP 相关的技术,于是查点资料,复习一下 ...

  8. AOP(Aspect Oriented Programming),即面向切面编程

    AOP AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善.OOP引入 ...

  9. AOP为Aspect Oriented Programming的缩写,意为:面向切面编程

    在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中的 ...

随机推荐

  1. Linux下磁盘分区挂载

    一般你去买vps都会看到介绍说硬盘多少G  比如 80G 但是你进入系统df -h的时候发现怎么只有10G呢, 其实这10G是用来装系统的和一些常用服务软件的  不是给你放网站数据的 那50G硬盘在哪 ...

  2. Spring Assert 断言

    Assert(断言)的初步理解构思 Web 应用在接受表单提交的数据后都需要对其进行合法性检查,如果表单数据不合法,请求将被驳回.类似的,当我们在编写类的方法时,也常常需要对方法入参进行合 法性检查, ...

  3. 12 抽象函数-abstract

    只有函数的定义,没有函数体的函数被称为抽象函数 1.抽象函数就是没有函数体的函数 2.抽象类使用abstract来定义 3.抽象类不能生产对象,但是可以拥有构造函数 abstract void fun ...

  4. 【HDU 4445】Crazy Tank(暴力)

    高中物理斜抛运动,简单分析一下角度固定下来则可以计算每个cannonball的降落坐标lnd. 因此暴力计算不同角度下的结果. #include <cstdio> #include &qu ...

  5. Linux基础2

    命令格式命令的类型:1.内部命令 linux系统使用的shell提供.2.外部命令 安装第三方软件才会. type 命令名 查命令的类型 | 把前边命令的输出作为后边命令的输入 管道 grep 对数据 ...

  6. Lock读写锁技术的妙用

    1.面试题1:三个线程读,三个线程写同一个数据 public class ReadWriteLockTest { public static void main(String[] args) { fi ...

  7. 【BZOJ-2115】Xor 线性基 + DFS

    2115: [Wc2011] Xor Time Limit: 10 Sec  Memory Limit: 259 MBSubmit: 2142  Solved: 893[Submit][Status] ...

  8. Android数据存储-文件操作

    一.预备知识 1.Android中的MVC设计模式 MVC (Model-View-Controller):M是指逻辑模型,V是指视图模型,C则是控制器.一个逻辑模型可以对于多种视图模型,比如一批统计 ...

  9. Ext FileSystem Family、Ext2、Ext3

    catalog . 简介 . Ext2文件系统 . Ext3文件系统 . 小结 1. 简介 VFS虚拟文件系统接口和数据结构构成了一个框架,各个文件系统的实现都必须在框架内运转,但这并不要求每个文件系 ...

  10. C#实体图片下载与批量下载(自动保存功能)

    新工作,第一个小任务,制作一个点击下载图片的功能.并提供批量下载操作.图片是字节流的形式,存放在数据库中的. 为了避免直接从数据库中,下载失败,会在本地保存一份. 进行压缩的是SharpZip这个压缩 ...