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
- Factoring out common cross-cutting code like logging, security verification
- Design by contract verification
- Non-intrusive profiling
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.
Comments