C# Corner
Clear Cross-Cutting Concerns with Aspect Oriented Programming in .NET
How to use the PostSharp AOP library to encapsulate cross-cutting concerns into efficient and re-usable modules.
Most applications have a number of cross-cutting concerns, such as logging, exception handling, transaction handling and security. These items do not deal with the core business logic of an application and are often times duplicated throughout the program. Aspect Oriented Programming (AOP) is a programming paradigm that is suitable for encapsulating cross-cutting concerns into re-usable modules.
There are a number of AOP libraries for .NET Framework, both open source and commercial. I will be covering PostSharp, which is a very flexible AOP library for .NET that has both a free community version as well as a professional version. First let's review the basics of Aspect Oriented Programming.
AOP Concepts and PostSharp Basics
An aspect represents a piece of cross cutting logic such as logging the entry and exit of a method call. An aspect is the execution of advice at pointcuts. Advice defines what is executed and when, and a jointpoint represents where a piece of advice is executed. A jointpoint could be a method call, a property getter or setter, an object constructor, and the like. A pointcut is a composition of different jointpoints where the advice will run. Now that you understand the basic concepts of the AOP model let's dive into how to use PostSharp to utilize the power of AOP in your .NET code.
PostSharp uses a technique known as static IL weaving in order to attach aspects at jointpoints. The actual weaving is done in a post build process. The main benefit of this strategy is that the resulting code executes very quickly. In Post Sharp an aspect is created as an attribute that can be attached at a jointpoint. Advices are implemented as methods within an aspect. Examples of advices in PostSharp are the entry and exit points of a method, object construction and type initialization.
Now let's get started on implementing our own aspect that will allow us to encapsulate logging logic for our application.
Logging Example
Logging is one of the most common cross-cutting concerns in an application. Using traditional object oriented programming we often end up with code like the following:
System.Diagnostics.Trace.WriteLine("Starting method X: with arguments Y,Z,...");
try
{
// core busness logic
}
catch(Exception ex)
{
System.Diagnostics.Trace.WriteLine(String.Format("Exception occurred in method {0} : {1}", ex.StackTrace, "X"));
}
System.Diagnostics.Trace.WriteLine("Finished method X with return value Y");
The code doesn't look that bad but it could end up being duplicated throughout a majority of the methods in your application. Furthermore, logging is not a concern of your core business logic. By utilizing AOP we can reduce the amount of code, improve code clarity and further seperate the concerns of logging and business logic. The end result is we can log any method by annotating it with a Log aspect attribute.
[Log]
public static int Add(int x, int y)
{
return x + y;
}
As you can see the Add method knows nothing about logging nor does it need too. We shift the concern of logging to the Log aspect. Let's take a step back and go over the full implementation of the Log aspect. First of all download and install PostSharp. Next create a new application and add a reference to PostSharp. Now we are ready to create our LogAttribute class that will represent our Log Aspect.
First we add our needed namespaces.
using PostSharp.Aspects;
using System.Diagnostics;
Next we apply the Serializable attribute to our aspect class, which is a prerequisite for any PostSharp implemented aspect.
[Serializable]
public class LogAttribute : OnMethodBoundaryAspect
{
Next we add advices to the aspect to intercept the entry and exit jointpoints of the target method. In the entry advice we will output "Starting method X with arguments: Y", where X is the method signature.
public override void OnEntry(MethodExecutionArgs args)
{
Trace.Write(string.Format("Starting method {0}", args.Method));
Trace.Write(" with arguments:");
for (int i = 0; i < args.Arguments.Count; i++)
{
Trace.Write(" " + args.Arguments[i]);
Trace.WriteIf(i != args.Arguments.Count - 1, ",");
}
Trace.Write(Environment.NewLine);
}
In the exit advice we output: "Finished method X with return value Y"
public override void OnExit(MethodExecutionArgs args)
{
Trace.WriteLine(string.Format("Finished method {0} with return value {1}", args.Method, args.ReturnValue));
}
Lastly, we add an advice to intercept any exceptions that occurred within the method call and output "Exception occurred in method X: Y", where X is the method name and Y is stack trace of the exception. We also set the flow behavior to continue to prevent re-throwing the exception. Simply remove args.FlowBehavior assignment if you would like to re-throw the exception in your application.
public override void OnException(MethodExecutionArgs args)
{
Trace.WriteLine(String.Format("Exception occurred in method {0}: {1}", args.Method.Name,
args.Exception.StackTrace));
args.FlowBehavior = FlowBehavior.Continue;
}
}
Applying the Logging Aspect
Now that we have defined a logging aspect, let's apply it to a test application. Our simple C# console example application drives a calculator class that can add, subtract, multiply and divide. First create an Aspects folder in the project and add the LogAttribute class from above with a namespace of AOPVSMSample.Aspects.
Next we implement our simple Calculator class and apply the Log aspect to each method.
Calculator.cs
using AOPVSMSample.Aspects;
namespace AOPVSMSample
{
public class Calculator
{
[Log]
public double Divide(int x, int y)
{
return x / y;
}
[Log]
public double Multiply(int x, int y)
{
return x * y;
}
[Log]
public double Add(int x, int y)
{
return x + y;
}
[Log]
public double Subtract(int x, int y)
{
return x - y;
}
}
}
Finally, we implement the calculator driver. We set up the tracer and add a Console.Out listener. Next we call each method on the calculator. When the application is compiled, PostSharp modifies the assembly to insert our Log aspect in the post build event.
Program.cs
namespace AOPVSMSample
{
class Program
{
static void Main(string[] args)
{
System.Diagnostics.Trace.Listeners.Add(new System.Diagnostics.TextWriterTraceListener(Console.Out));
Calculator calc = new Calculator();
calc.Add(1, 1);
calc.Subtract(72, 42);
calc.Multiply(2, 21);
calc.Divide(100, 0);
}
}
}
When we run the test application, we see that the Log aspect is correctly executed for each method call, as well as catching the divide by zero exception.
[Click on image for larger view.] |
Figure 1. |
Conclusion
Aspect Oriented Programming is a powerful paradigm that is complementary to object oriented programming. AOP is well versed for representing templates and cross-cutting concerns. I hope you have seen some of the power AOP can wield. If you would like to see further discussion of AOP or more complex examples, please post a comment.
¬
About the Author
Eric Vogel is a Senior Software Developer for Red Cedar Solutions Group in Okemos, Michigan. He is the president of the Greater Lansing User Group for .NET. Eric enjoys learning about software architecture and craftsmanship, and is always looking for ways to create more robust and testable applications. Contact him at [email protected].