zondag 5 november 2006

Aspect Oriented Programming in .NET

A while ago, there was somebody who asked the question on a programming forum whether it was possible to retrieve the values of the arguments that are passed to a method in .NET. The purpose was to create some kind of a 'logging' system so that he could log which methods have been called, and what values were passed to those methods.
This person had already created a method that retrieved all kinds of information of a certain method, but getting the values of the parameters via reflection was not possible.

The disadvantage of this approach is that your methods are being polluted by this logging method. You always have to add a call to this logging method in your 'business methods'.
For instance:

public void SomeMethod()
{
LogThisMethod (MethodBase.GetCurrentMethod());

// Do the real work here.
}

The call to the LogThisMethod method is not likely a core concern in the application, yet, if you want to log calls to certain methods, you’ll have to write a call to this method in every method that you want to log.
In other words: the logging is a cross-cutting concern because it is an aspect of our program that has nothing to do with the core-problem that is to be solved by our program and it appears in multiple parts of the program.

Luckily, there's a much cleaner approach to solve this problem. Aspect Oriented Programming offers a way to separate cross-cutting concerns like logging in a much cleaner way.
AOP allows you to remove the cross-cutting concerns from your 'business code', and create an 'aspect' for it instead.
This ‘aspect’ will then be weaved into your code at runtime which means that you do not have to call it yourself in the core parts of the application.
In this way, the cross-cutting concerns can be decomposed from the core logic of the application and this will result in more readable and better maintainable software.

In .NET, you can use the Spring.NET framework to apply Aspect Oriented Programming.
In the examples that follow, I’ll be using the Spring.NET framework.

You can solve the logging-problem that I've mentioned earlier using AOP in C# in the following way:

Suppose we have a class 'TestClass' and we want to log every method that is being invoked in this class. Our TestClass looks like this:

public interface ITest
{
void SayHello( string name );
void Shout( string message );
}

public class TestClass : ITest
{
public void Method1( string name )
{
Console.WriteLine ("Hello " + name + " ! ");
}

public void Shout( string message )
{
Console.WriteLine (message + "!!!!!!!");
}
}

These are the steps that have to be taken to create some kind of logging functionality using AOP:


  • Create an Advice that takes care of the logging. An Advice describes a certain ‘procedure’ that must be executed at certain points (joinpoints) in the application. For instance: an Advice can be executed at the entry point of a method.

    If you use Spring.NET, you can create a class which implements the IMethodBeforeAdvice. This will make sure that this Advice is called before a method-call.
    The Advice can look like this:

    public class MethodInvocationLoggingAdvice : IMethodBeforeAdvice
    {

    public void Before( System.Reflection.MethodInfo method,
    object[] args, object target )
    {
    string message = method.Name + " called with ";

    string arguments = string.Empty;

    for( int i = 0; i < args.Length; i++ )
    {
    arguments += args[i] +", ";
    }

    Console.WriteLine (message + arguments.SubString (0, arguments.Length - 2));
    }
    }

    Now, we have separated the logging logic in a separate class.

  • Tell our program to use the Advice
    In our program, we must indicate that our Advice has to be called when we invoke the methods of a certain class.
    Using Spring.NET, we can do this with only 3 lines of code:
    static void Main()
    {
    ProxyFactory f = new ProxyFactory (new TestClass());

    f.AddAdvice (new MethodInvocationLoggingAdvice());

    ITest t = (ITest)f.GetProxy();

    t.SayHello ("Frederik");

    t.Shout ("Watch out");
    }

    The beautiful thing is, that we've kept our 'business methods' clean and every time we invoke a method, the logging functionality is called. If we extend our TestClass with a couple of new methods, we do not have to worry about this logging functionality, since those new methods will also call our MethodInvocationLoggingAdvice as well.

But, what if you only want to log invocations of certain methods, instead of logging every method call? This can also be done rather easily by defining a PointCut. Everytime the pointcut is reached, our Advice will be executed.
.NET attributes provide a great way to define PointCuts.

Building on the previous example, we can extend our code so that the MethodInvocationLoggingAdvice is only called when a method is decorated with a specific Attribute. For instance: only invocations of methods that have the 'Log' attribute, must be logged.
To do this, we must first create this Log attribute:

[AttributeUsage(AttributeTargets.Method)]
public class LogAttribute : Attribute
{
}

We can now change the TestClass to indicate that only method-calls to the Shout method should be logged:
public class TestClass : ITest
{
public void Method1( string name )
{
Console.WriteLine ("Hello " + name + " ! ");
}

[Log]
public void Shout( string message )
{
Console.WriteLine (message + "!!!!!!!");
}
}

All what's left to do, is to make a change to the code that will be responsible of weaving the advice into our code. We must now indicate that our Advice should only be executed on methods that have the Log attribute.
static void Main()
{
ProxyFactory f = new ProxyFactory (new TestClass());

f.AddAdvisor (new DefaultPointCutAdvisor (
new AttributeMatchMethodPointcut (typeof(LogAttribute),
new MethodInvocationLoggingAdvice()));

ITest t = (ITest)f.GetProxy();

t.SayHello ("Frederik");

t.Shout ("Watch out");
}

When you execute this program, you'll see that only the method-call to 'Shout' is being logged.



6 opmerkingen:

P.J. van de Sande zei

Then you still need to add extra code to log. Why not using IL Emiting? Just add an LogThis attribute on your class and in startup time inject the classes with some extra IL.

This is much clearer imho.

Frederik Gheysels zei

[quote]Then you still need to add extra code to log.[/quote]
What do you mean ? The Advice class contains the logging logic, and this 'aspect' is 'weaved' at runtime in you r application. That's all there is to it.

How do you see this IL emitting solution, and why would it be easier to understand ?

Dimitri zei

P.J Van De Sande, I think you don't get the idea of AOP. :-)
The idea of AOP is that one can write crosscutting concerns in a separate module. The immediate effect of this is that your whole application will be far better maintainable.

I don't think that using IL emitting is the right way to do this, because there are more issues related with AOP than just inserting some code before/after a method.

On example example is that it is also possible to put extra behaviour around a method. In your around advice, you can make a call to the original method, even with other argument values.
I don't think that this would be very easy to do using IL emitting. And what about the maintainability of this solution.

I haven't used Spring.NET, but maybe this framework uses IL emitting when weaving the aspects.
Furthermore, there are more issues related with AOP than just inserting a little bit of code in your application.

Geert Baeyaert zei

Why is

[Log]
public void MyMethod(...)
{
...
}

better than

public void MyMethod(...)
{
Log();

...
}

Frederik Gheysels zei

The logging in this example is offcourse a simple cross-cutting concern.
However, decorating a method with an attribute still has an advantage over explicitely calling a log-method: what if you want to disable the logging functionality ? If you log method-calls by making a call to some kind of a 'log-method', you'll have to find all the methods for which you're logging the calls, and comment the 'Log();' line out.
Using an aspect, it is much less work to disable the logging. It's just a matter of changing one line of code. It should even be possible to disable the logging at runtime.

In the example in my post, it may seem that writing a method call or an attribute doesn't make much of a difference, since they're both just one line of code.
Suppose you want to extend your logging functionality so that it is more like a 'log-tracing' system, where you log every method-entry and method-exit. Here, it will not be sufficient to just call one logging method, you'll need two:
[code]
public void TheMethod()
{
LogEntry();
...
LogExit();
}[/code]
When using aspects, it's just a matter of changing the advice.

The same is true for transaction-handling: If you use Aspects to implement transaction handling, your aspect will contain more 'logic', and it is not possible to replace the aspect with just one line of extra code. (Think of the declarative way of transaction handling that is possible in COM+).

Another advantage of using the logging aspect in my simple example, is that you do not pollute your method-body with calls to methods that handle cross-cutting concerns (even if it is just one simple line of code). By using an attribute, it is -imho- easier to understand that the method-call will be logged and you'll not be distracted with this logging aspect when reading the method body.

Geert Baeyaert zei

The argument about adding log tracing (LogEntry(); ... LogExit);) is the clincher, I think.
That would indeed not be possible with a single method call.