Aspect Oriented Programming using .NET

Aspect Oriented Programming (AOP) is an interesting concept that can be applied to many of the programming problems we solve everyday. In our Visual Studio team system code, we have a lot of web services and remoting code that essentially does the following:

public void MyMethod(int parameter)
{
Trace.EnteredMethod("MyMethod", parameter);
SecurityCheck();
// Bunch of processing
Trace.ExitMethod("MyMethod");
}


This is not just peculiar to our domain but is seen across different domains. In OO programming, classes and methods are designed for performing specific operations and common/duplicate functionality are factored out into common classes. However, there are cross-cutting concerns that span across all classes and methods, like logging and security checks. OOP only partially solves this problem by requiring users to define separate classes for logging and security checks and requiring each class/methods needing these services to call them. AOP targets and solves this problem elegantly.

AOP divides code into base-code (code for your functionality) and a new construct called aspect. Aspect encapsulates these cross-cutting concerns using the following concepts:
· join-points: Points in the structure of base-code where the cross-cutting functionality needs to execute. This is typically when specific methods are entered or exited or properties are accessed;
· point-cut: A logical description of join-points using some specific syntax; and
· advice: additional code like logging and security check that each of these methods need to perform.

The most mature AOP language is probably AspectJ, which adds AOP extensions to Java. However, for this blog, I'd stick to .NET languages like AspectDNG, Aspect# and C#.

Language support for AOP

AOP support has to be built into the language and/or framework because it is based on method call interception. Whenever a method is called, the framework needs to provide a stub to call some other piece of code. Though .NET CLR has this capability, it is intrusive as you need an object to extend from MarshalByRefObject or ContextBoundObject to allow method-interception. This is a serious limitation because each class needs to be written so that it supports AOP. Many AOP languages or language-extensions get around this limitation by using various techniques, which generally fall into two broad categories – runtime or dynamic weaving and compile-time or static weaving.

Aspect# is an AOP language that uses static compile time weaving. It uses its own proxy (and not CLR's proxy) called DynamicProxy. DynamicProxy is generated at compile time and works differently while proxying interfaces (generates dynamic class and delegates method calls to the target of invocation) and proxying classes (generates stub class that inherits from the target).

Aspect# provides syntax to define point-cut and call method-interceptors or advice for them. It is done as shown in code 1.

Code 1

import YourCompany.CMS.ContentProviders in YourCompanyAssembly
import YourCompany.CMS.Aop.Interceptors

aspect SecurityAspect for RSSContentProvider
include Mixins.SecurityResourceImpl in MyMixinsAssembly

pointcut method(* MyMethod(*))
advice(TracingInterceptor)
end


end
public class TracingInterceptor : ImethodInterceptor
{
public object Invoke(IMethodInvocation invocation)
{
// Trace using information from IMethodInvocation
// like Method, MethodInvocationTarget
return invocation.Proceed();
}
}


The important bits are marked in bold. The first block is the point-cut in a Ruby like syntax, which specifies all methods with the name MyMethod to be included in the join-points. The second block is the interceptor (advice) TracingInterceptor. The TracingInterceptor is a class that has to implement the IMethodInterceptor. It can call invocation.Proceed to continue with the method invocation once it is through with the tracing. So, whenever the MyMethod is called, TracingInterceptor.Invoke gets called.

Other languages like AspectDNG (.NET-based AOP language-extension) accomplish this using something called IL weaving. In this, the target or base-code is coded in any language that can be compiled into MSIL, e.g. C#, VB.NET, J#, etc. So, the target code would look like:

sing System;

public class MyClass {
public int ProcessString(String s, out string outStr) {
// ... 
}
}


There is no special code or any type of modification needed on the base-code as evident from above, which is plain-vanilla C# code. The aspect code is written as in code 2, which can also be C# code and needs some additional assembly reference and attribute decoration for AspectDNG to pick them up.

Code 2

using DotNetGuru.AspectDNG.MetaAspects;
using DotNetGuru.AspectDNG.Joinpoints;
using System;
public class AspectsSample{
[AroundCall("* MyClass::ProcessString(*)")]
public static object YourMethodCallInterceptor(JoinPoint jp) {
Console.WriteLine("Code before calls to '.. MyClass.ProcessString(..)'");
object result = jp.Proceed();
Console.WriteLine("Code after calls to '.. MyClass.ProcessString(..)'");
return result;
}
}

Here, point-cut is specified using attributes like AroundCall and AroundBody. Both the base-code and aspect code are compiled separately into different assemblies using respective compilers, like csc into Target.exe and aspect.dll. Then, the aspectdng.exe tool can be employed, which uses reflection to reach to the attribute in the aspect code to weave call to them so that a new assembly called Target-weaved.exe is created. In target-weaved.exe, AspectDNG directly puts in calls to the aspects around the target code by inserting/replacing IL code wherever required.

There are some AOP languages like Encase, which apply the aspects at run-time. These languages use AOP frameworks that reads in configuration files for point-cuts and at run-time generate proxy classes that intercept calls, allows advice of the aspect to execute first and then invokes the actual target. The benefit is that there is no edit-compile cycle but it faces performance issues.

AOP in C#

Till now we were talking about non-mainstream languages to get AOP done. However, by doing a wee bit of extra work, we can get the same functionality in C# as well. The limitation with CLR is that it allows method interception only when the classes containing the methods inherit from MarshalByRefObject or ContextBoundObject. When a class inheriting from ContextBoundObject is activated, the .NET interceptor comes into play. It creates a transparent-proxy and a real-proxy. The transparent-proxy gets called for all invocation of the target, serializes the call stack and passes that on to the real-proxy. The real-proxy calls the first message sink, which is an object implementing the IMessageSink interface. It is the duty of this first message sink to call the next until the final sink goes and calls the actual target. In this sink chaining, we can insert objects that can execute our aspect advice.

Another limitation with C# is that there is no way in C# syntax to specify join-points. We will circumvent these two limitations by inheriting the target classes from ContextBoundObject. We will use attributes on specific classes so that all methods and field-setters in them become included in the join-points. Refer code 3.

Code 3

using System;
// Include the aspect framework
using Abhinaba.Aspect.Security;

[Security()]
public class MyClass : ContextBoundObject {
public int ProcessString(String s, out string outStr) {
Console.WriteLine("Inside ProcessString");
outStr = s.ToUpper();
return outStr.Length;
}
}


Here Security is an attribute defined in our Abhinaba,Aspect.Security namespace, which pulls in our support for AOP and includes the current class and all its methods in the join-points. The whole AOP framework looks as in code 4. All the important parts are marked in bold.

Code 4

using System;
using System.Diagnostics;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Contexts;
using System.Runtime.Remoting.Activation;
namespace Abhinaba.Aspect.Security
{
internal class SecurityAspect : IMessageSink {
internal SecurityAspect(IMessageSink next)
{
m_next = next;
}

private IMessageSink m_next;

#region IMessageSink implementation
public IMessageSink NextSink
{
get{return m_next;}
}
public IMessage SyncProcessMessage(IMessage msg)
{
Preprocess(msg);
IMessage returnMethod = m_next.SyncProcessMessage(msg);
return returnMethod;
}
public IMessageCtrl AsyncProcessMessage(IMessage msg, 
IMessageSink replySink)
{
throw new InvalidOperationException();
}
#endregion //IMessageSink implementation

#region Helper methods
private void Preprocess(IMessage msg)
{
// We only want to process method calls
if (!(msg is IMethodMessage)) return;
IMethodMessage call = msg as IMethodMessage;
Type type = Type.GetType(call.TypeName);
string callStr = type.Name + "." + call.MethodName;
Console.WriteLine("Security validating : {0} for {1}",
callStr, Environment.UserName);
}
#endregion Helpers
}
public class SecurityProperty : IContextProperty, 
IContributeObjectSink
{
#region IContributeObjectSink implementation
public IMessageSink GetObjectSink(MarshalByRefObject o, 
IMessageSink next)
{
return new SecurityAspect(next);
}
#endregion // IContributeObjectSink implementation
#region IContextProperty implementation
// Implement Name, Freeze, IsNewContextOK
#endregion //IContextProperty implementation
}
[AttributeUsage(AttributeTargets.Class)]
public class SecurityAttribute : ContextAttribute
{
public SecurityAttribute() : base("Security") { }
public override void GetPropertiesForNewContext(
IConstructionCallMessage ccm)
{
ccm.ContextProperties.Add(new SecurityProperty());
}
}
}


SecurityAttribute derives from ContextAttribute and MyClass derives from ContextBoundObject. As such, even before the ctor of the class is called, the framework instantiates SecurityAttribute and calls GetPropertiesForNewContext, passing it a reference to IConstructionCallMessage. SecurityAttribute creates an instance of SecurityProperty and adds it to the context. This addition makes the framework call the various IContextProperty methods that SecurityProperty implements and then calls the ctor of MyClass.

After this, the first time any MyClass method or variable is referenced, it calls GetObjectSink method of SecurityProperty through its IContributeObjectSink interface. This method returns a newly created instance of SecurityAspect. Till this point, you can consider everything as initialization code and SecurityAspect implements our main functionality for AOP advice.

When the instance of SecurityAspect is created, its constructor is passed a reference to the next message sink so that all the sinks can be chained and called one after the other. After this, SyncProcessMessage is called, which is our main method interceptor and where all processing is done. After performing all processing like security verification, the code calls the target method. It can then refer to the return value and undertake post-processing. With this, we have AOP implementation albeit some intrusive code as the target codes needs to be modified for AOP support.

Possibilities

AOP is a very generic programming method and can be used in a variety of situations. Some of them are as follows:
· Factoring out common cross-cutting code like logging, security verification;
· Design by contract verification; and
· Non-intrusive profiling.

Sample code
The sample solution (VS2005), including all sources, is available at the following URL: http://abhinaba.members.winisp.net/blog/Sources/AOPSample.zip. It contains sources for two different aspects, one for security and one for tracing both applied on the same class. I have applied conditional compilation attribute to the tracing aspect so that on, release build tracing gets disabled.




Added on May 30, 2007 Comment

Comments

Post a comment