The Enterprise Library created by the Patterns & Practices group of Microsoft contains a building block called Policy Injection to inject policy-like aspects into object method calls. The opportunity to intercept method calls (to inject code between the caller and the called method) is in .NET still from version 1.0. If I also mention that the remoting mechanism is built on it, it does not seem so surprising.
However, injecting code between the caller and the called method is just one part of what we call Aspect-Oriented Programming, it is quite important. The pattern used by the Enterprise Library’s Policy Injection Block is one possible implementation. In this post I will show you another technique. This post shows only the solution, the next part is going to treat the background.
What is the point we want to reach?
We are going to create a solution to inject logging code for certain methods. The class we are going to call methods from (LoggedObject) has the following source code:
[LoggerAspect]
public class LoggedObject: ContextBoundObject
{
public void Method1()
{
Console.WriteLine("Calling Method1");
}
public int Add(int a, int b)
{
return a + b;
}
public int Divide(int a, int b)
{
return a/b;
}
public void SkipLoggingThis()
{
Console.WriteLine();
Console.WriteLine("This method has not been logged by the aspect.");
}
}
In this simple solution we will log the LoggedObject methods by displaying the name of the method with the input and return values to the screen. We are going to handle the exception calling the Divide method with zero divider. We exclude the SkipLoggingThis method from the scope of logging. To test how logging works we use the following console application:
using System;
namespace AspectedObjectTest
{
class Program
{
static void Main(string[] args)
{
LoggedObject obj = new LoggedObject();
obj.Method1();
obj.Add(5, 6);
obj.Divide(24, 6);
try
{
obj.Divide(100, 0);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
obj.SkipLoggingThis();
}
}
}
This application creates the following output:
Before Method1()
Calling Method1
After Method1
Before Add(System.Int32, System.Int32)
a = 5
b = 6
Return value: 11
After Add
Before Divide(System.Int32, System.Int32)
a = 24
b = 6
Return value: 4
After Divide
Before Divide(System.Int32, System.Int32)
a = 100
b = 0
Return value:
After Divide
Zero is used as divider!
This method has not been logged by the aspect.
You can observe that the console output contains much more information written out by the source code of the LoggedObject class. How could it be? The output is generated by the code injected between the LoggedObject methods and its callers.
Implementation basics
We inject code into the call of a method by using a proxy and a stub. This works so that we change the memory footprint of method call pushed to the call stack into a message by serializing an object representing the method call and its parameters. This message is sent by the proxy to the stub where it is deserialized and pushed back to the stack. The stub calls the method and retrieves its return value to the proxy using the stack value-message transformation.
The real process is much more complicated and is treated in all details in the “Advanced Methods” chapter of Don Box’s “Essential .NET Volume 1” book. In the next part of the article I will give you a brief overview of this mechanism but Don’s interpretation is much more authentic than mine J.
The solution I used to implement the logging mechanism is based on a proxy class of my own. I “ask” the .NET framework to use my proxy. I derive the LoggedObject class from the ContextBound class and decorate the class with an attribute to inject my proxy.
// --- This is the object using my own proxy
[LoggerAspect]
public class LoggedObject: ContextBoundObject { ... }
// --- This is the atrtribute class to inject my proxy
[AttributeUsage(AttributeTargets.Class)]
public class LoggerAspectAttribute : ProxyAttribute
{
public override MarshalByRefObject CreateInstance(Type serverType)
{
RealProxy spProxy =
new LoggerAspectProxy(base.CreateInstance(serverType), serverType);
return (MarshalByRefObject)spProxy.GetTransparentProxy();
}
}
// --- This proxy is responsible for the log mechanism
public sealed class LoggerAspectProxy: AspectInjectionProxy { ... }
public class AspectInjectionProxy: RealProxy { ... }
When the .NET framework calls any of the LoggedObject methods it recognizes that the object is derived from the ContextBound type and so all method calls should be handled through a proxy mechanism. It also recognizes that the class is decorated with an attribute deriving from the ProxyAttribute type. It is a sign to change its default proxy to the one instantiated by the CreateInstance method of the LoggerAspectAttribute.
This proxy is LoggerAspectProxy; it implements the functions forming the aspect. This class is inherited from the AspectInjectionProxy class defining a generic frame to fill with functionality.
Creating the proxy class
The key of the whole mechanism is the AspectInjectionProxy class. If you want to create your own aspects, derive your own proxy from this class. The blueprint of the proxy class is quite simple; however, its implementation is a bit more complex.
public class AspectInjectionProxy: RealProxy
{
public AspectInjectionProxy(MarshalByRefObject target, Type type):
base(type) { ... }
public override IMessage Invoke(IMessage request) { ... }
protected virtual bool IsAspectedMessage(IMethodCallMessage call) { ... }
protected virtual void BeforeOperationAspect(IMethodCallMessage call,
ref bool cancel) { ... }
protected virtual void AfterOperationAspect(IMethodReturnMessage response,
bool successful) { ... }
protected virtual Exception HandleExceptionAspect(Exception ex) { ... }
}
AspectInjectionProxy derives from RealProxy. We use the Invoke method through the proxy that contains the message representing the original call with its input parameters in the argument with the IMessage type. According to this fact, the lion’s share of the work is carried out by Invoke. The class defines a few virtual methods to define the behavior of the aspect in derived classes.
The essential code for AspectInjectionProxy is the following:
public class AspectInjectionProxy: RealProxy
{
private readonly MarshalByRefObject _Target;
// --- Eltároljuk azt az objektumpéldányt, amelyet a proxy hívni fog.
public AspectInjectionProxy(MarshalByRefObject target, Type type): base(type)
{
_Target = target;
}
We receive the object instance to use in the target parameter of the constructor; we simply store it for later use.
public override IMessage Invoke(IMessage request)
{
IMethodReturnMessage response;
IMethodCallMessage call = (IMethodCallMessage)request;
IConstructionCallMessage ctor = call as IConstructionCallMessage;
if (ctor != null)
{
// --- This is the call to the constructor, initialize the object
RealProxy defaultProxy = RemotingServices.GetRealProxy(_Target);
response = defaultProxy.InitializeServerObject(ctor);
if (response.Exception != null)
{
return new ReturnMessage(response.Exception, call);
}
// --- Call the contructor behind the transparent proxy
ContextBoundObject tp = (ContextBoundObject)GetTransparentProxy();
response = EnterpriseServicesHelper.CreateConstructionReturnMessage(ctor, tp);
}
else
{
// --- Check, if the method is a business operation or not
if (IsAspectedMessage(call))
response = OperationMethodCall(call);
else
response = RemotingServices.ExecuteMessage(_Target, call);
}
return response;
}
The Invoke method checks if we call the constructor of the object instance or a method of an already constructed object. If the constructor is called, some “special” code is executed to create the object instance and prepare to use our injected code; in this case we ignore the aspects.
If a method on the existing object instance is called, the IsAspectedMessage method checks if we intend to use a method with or without aspects. Should we use aspects, the OperationMethodCall is used. Otherwise the ExecuteMessage static method of the RemotingServices class runs the “addressed” method in our object instance.
The result is retrieved in the response variable having a type of IMethodReturnMessage. In this way the method result goes back to the caller method just like if no proxy would be used. OperationMethodCall is responsible to invoke aspects:
private IMethodReturnMessage OperationMethodCall(IMethodCallMessage call)
{
try
{
bool cancel = false;
BeforeOperationAspect(call, ref cancel);
if (cancel) return new ReturnMessage(null, call);
IMethodReturnMessage response = RemotingServices.ExecuteMessage(_Target, call);
if (response.Exception != null)
{
response = new ReturnMessage(HandleExceptionAspect(response.Exception), call);
}
AfterOperationAspect(response, response.Exception == null);
return response;
}
catch (Exception ex)
{
Exception handledEx = HandleExceptionAspect(ex);
return new ReturnMessage(handledEx, call);
}
}
We use ExecuteMessage to run the original method. In the body of BeforeOperationAspect we provide a way for the aspect to abort the method call by using the cancel output parameter with a value of true. The AfterOperationAspect method can be used to carry out the tasks after the method has been run. In its second bool parameter we can pass a flag indicating the success or failure of the method. In this implementation we take the method into account as successful if it does not raise any exception.
The HandleExceptionAspect allows us managing the exceptions raised during method calls. We can handle them in two ways: if HandleExceptionAspect returns null, it means the exception is handled. In other cases the exception instance returned will passed back to the caller method. Please note, this exception handling mechanism catches all exceptions independently of where they are raised: either in the original method or in the aspects.
From here the other AspectInjectionProxy methods are simple:
// --- Should we use aspects for the method?
protected virtual bool IsAspectedMessage(IMethodCallMessage call)
{
return true;
}
// --- Aspect called before the method
protected virtual void BeforeOperationAspect(IMethodCallMessage call, ref bool cancel)
{
}
// --- Aspect called after the method
protected virtual void AfterOperationAspect(IMethodReturnMessage response,
bool successful)
{
}
// --- Managing expections
protected virtual Exception HandleExceptionAspect(Exception ex)
{
return ex;
}
}
Using the aspect in the log implementation
To implement the logging functionality introduced in the beginning of the article we simply must derive our proxy class from AspectInjectionProxy. It is simple; we override the virtual methods to define the behavior of aspects.
public sealed class LoggerAspectProxy: AspectInjectionProxy
{
public LoggerAspectProxy(MarshalByRefObject target, Type type) :
base(target, type) { }
// --- We do not use aspects for methods starting with “Skip”
protected override bool IsAspectedMessage(IMethodCallMessage call)
{
return !call.MethodName.StartsWith("Skip");
}
// --- Log the method signature with current parameter values
protected override void BeforeOperationAspect(IMethodCallMessage call, ref bool cancel)
{
StringBuilder sb = new StringBuilder();
// --- Output the signature
sb.AppendFormat("Before {0}(", call.MethodName);
bool isFirst = true;
foreach (Type paramType in (Type[])call.MethodSignature)
{
if (!isFirst) sb.Append(", ");
isFirst = false;
sb.Append(paramType.FullName);
}
sb.Append(")");
Console.WriteLine(sb);
// --- Output parameter values
for (int i = 0; i < call.InArgCount; i++)
{
Console.WriteLine(" {0} = {1}", call.GetInArgName(i), call.GetArg(i));
}
}
// --- Output the return value of the method
protected override void AfterOperationAspect(IMethodReturnMessage response,
bool successful)
{
if ((response.MethodBase as MethodInfo).ReturnType != typeof(void))
{
Console.WriteLine(" Return value: {0}", response.ReturnValue);
}
Console.WriteLine("After {0}", response.MethodName);
Console.WriteLine();
}
// --- Transform the DivideByZeroExcpetion
protected override Exception HandleExceptionAspect(Exception ex)
{
if (ex is DivideByZeroException)
{
return new InvalidOperationException("Zero is used as divider!");
}
return ex;
}
}
As you can see the aspect methods can access the input parameter values of the original methods through the IMessage interface (GetInArgName, GetInArg) just as the return value (ReturnType) so outputting them is pretty simple.
Where we are?
The code shown in this article is relatively simple and provides a lot of opportunity for future development. Aspect methods can defined according to your intention they could carry out even complex operations. I have successfully used them for business infrastructure aspects like transaction handling, functional and diagnostic logging, business rule checks, managing aspect pipelines, etc.
However this solution has some issues you must be aware of:
Aspected objects must derive from ContextBound class.
Leaving the proxy attribute out from the aspected class prevents our aspects running.
The message-stack and vice versa transformations have performance penalty.
In the next part I will summarize the background behind the aspect mechanism and examine the performance issues.
Posted
Aug 11 2008, 08:00 AM
by
inovak