Aspect Oriented Programming using .NET

Some time back I wrote an article on AOP using .NET languages. It's a long post so bear with me. In case you are interested only about C# go down here

What is AOP

Aspect Oriented Programming or 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 accross 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: The 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
  • 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 in to the language and/or framework because it is based on method call interception. Whenever a methods is called the framework needs to provide a stub to call some other piece of code. Though .NET CLR has this capability, but 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. The techniques generally fall into two broad categories runtime or dynamic weaving, compile-time or static weaving.

Aspect# is AOP language which uses static compile time weaving. It uses its own proxy (and not CLR's proxy) called DynamicProxy. DynamicProxy is generated 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's done as follows 

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

aspect SecurityAspect for RSSContentProvider
     include Mixins.SecurityResourceImpl in MyMixinsAssembly




          pointcut method(* MyMethod(*))<br>           advice(TracingInterceptor)<br>     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's done with the tracing. So whenever the MyMethod is called TracingInterceptor.Invoke gets called.

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

 using 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 follows which can also be C# code and needs some additional assembly reference and attribute decoration for AspectDNG to pick them up

 using DotNetGuru.AspectDNG.MetaAspects; using DotNetGuru.AspectDNG.Joinpoints; using System;public class AspectsSample{     [AroundCall(  "* MyClass::ProcessString(*)"  )]     publicstaticobject 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, AroundBody. Both the base-code and the aspect code are separately compiled into different assemblies using respective compilers like csc into Target.exe and aspect.dll. Then the aspectdng.exe tool can be used 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 which reads in configuration files for point-cuts and at runtime 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 not 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 bit 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 trasparent-proxy and a real-proxy. The transparent-proxy gets called for all invocation of the target. The transparent proxy 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. Its 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 which 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'll use attributes on specific classes so that all methods and field-setters in them become included into the join-points.

 using System;// Include the aspect frameworkusing 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 follows. All the important parts are marked in bold.

 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;}
        }        publicIMessage SyncProcessMessage(IMessage msg)      {     Preprocess(msg);     IMessage returnMethod = <br>                              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)        {            returnnewSecurityAspect (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( newSecurityProperty ());         }    }
} 

SecurityAttribute derives from ContextAttribute and MyClass derives from ContextBoundObject, due to this even before the ctor of the class is called the framework instantiates SecurityAttribute and calls the 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 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 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 doing all processing like security verification the code calls the target method. Then it can refer to the return value and do 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 situation. Some of them are as follows

Sample code

The sample solution (VS2005) including all sources are available here. 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.