Aspect Oriented Programming using .NET

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

        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, 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 clicking the download link on the top left. 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.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” - Martin Fowler