Introduction
In this article, I've used PostSharp which is one of the most friendly tools to weave codes at compile time in order to develop a few non-functionals like Exception Handling, Logging, Execution Timing, and Transaction Management. It allows using AOP in your C# code with a very simple and intuitive syntax. I have seen that many developers repeat aforementioned blocks of code throughout their application and consequently their code is difficult to understand and to change. In addition, AOP complements OOP and in order to remove intermingled codes which appear in every class, AOP is recommended strongly. I've demonstrated how to use AOP to reduce lines of code and create programs which are easy to read and manage.
Note: Remember to set the TestConnectionString
in the app.config and run the Script.Sql in your system prior to running the sample code.
Requirements
To follow along with this article, you will need the following installed:
- Visual Studio 2010 or higher and .NET 4.0 or higher. Visual Studio Express will allow for PostSharp’s post-compilation support so the Aspects you write will work, but the PostSharp IDE extensions are not available for Express.
- PostSharp 2.1. It can be downloaded from http://www.sharpcrafters.com/postsharp/download.
- Sql Server 200x or Sql Express Edition.
- The source code provided by this article.
Background
In AOP, there are a few terms which are essential to know. Defining these terms are more difficult than implementing them. However, I will define them briefly and their responsibility will be clarified later in sample code. I'll discuss the most important ones.
1. Crosscutting Concerns
Referred to intermingled codes which every class contains. For instance, in the following code, Exception Handling, and Tracing blocks are called intermingled codes and can be moved into an aspect.
void AddRecord(string firstName, string lastName, string email)
{
Try
{
}
Catch(Exception ex)
{
Trace.WriteLine("Exception during transaction: " + ex.ToString());
}
}
2. Aspect
Aspects will alter behavior to other classes or objects during compile time or run time. They are called either weaving a class or object. In our examples, which will be discussed later, aspects are defined as attributes and will be declared on top of the methods or classes to add some functionality to those methods or classes.
3. Joinpoint
We can consider every point in our code in which another method is called as a Joinpoint
.
4. Pointcut
A way of defining Joinpoint
s in our code. Pointcut
also contains an advice that is to occur when the joinpoint
is reached. So if we define a Pointcut
on a particular method being invoked, when the invocation occurs or the joinpoint
is invoked, it is intercepted by the AOP framework and the pointcut
's advice is executed.
5. Advice
In our code, when we reach a Joinpoint
, we will invoke another method or methods. These method(s) are considered as Advices.
Using the Code
Our sample is a C# Console application. As can be seen from the following picture, we need to add a reference to PostSharp.Dll. In addition, There are four different aspects (in Aspects folder) in our code which can be seen below:
First of all, I'll discuss Timing Aspect. In many applications, it is important for developers or users to calculate execution time of a piece of code. In a traditional method, we used to use the following code to calculate such a value:
_StopWatch = Stopwatch.StartNew();
ExecuteSomeCode();
Console.WriteLine(string.Format("It took {0}ms to execute", _StopWatch.ElapsedMilliseconds));
But in every application, we may have to repeat such a code throughout our application in order to get elapsed time in different methods. AOP will assist us to diminish such codes. In this scenario, we will create an aspect and use it as an attribute on top of every method for which its execution time is needed, as below:
[Serializable]
[MulticastAttributeUsage(MulticastTargets.Method)]
public class TimingAspect : PostSharp.Aspects.OnMethodBoundaryAspect
{
[NonSerialized]
Stopwatch _StopWatch;
public override void OnEntry(PostSharp.Aspects.MethodExecutionArgs args)
{
_StopWatch = Stopwatch.StartNew();
base.OnEntry(args);
}
public override void OnExit(PostSharp.Aspects.MethodExecutionArgs args)
{
Console.WriteLine(string.Format("[{0}] took {1}ms to execute",
new StackTrace().GetFrame(1).GetMethod().Name,
_StopWatch.ElapsedMilliseconds));
base.OnExit(args);
}
}
Note: Every aspect should be Serializable
so [Serializable]
attribute is used for every Aspect
class.
Here I've used TimingAspect
aspect as an attribute:
[TimingAspect]
static void LongRunningCalc()
{
Thread.Sleep(1000);
}
TimingAspect
inherits from the OnMethodBoundaryAspect
type. That type, explained in far greater detail in the extensive online PostSharp documentation, provides opportunities to intercept the code of a method and to execute code prior to it, after it, on success, or on failure only. OnEntry
, OnExit
are our Advices which will be invoked when LongRunningCalc
is invoked and finished. In this case, the point in code where LongRunningCalc
is invoked, is a JoinPoint
. OnEntry
is invoked when LongRunningCalc
is invoked by PostSharp
and OnExit
is invoked when the execution of LongRunningCalc
finished. Here is the result of LongRunningCalc
execution:
LogAspect
is quite similar to TimingAspect
as it inherited from the OnMethodBoundaryAspect
and override OnEntry
, OnExit
as below:
[Serializable]
public class LogAspect : OnMethodBoundaryAspect
{
public override void OnEntry(MethodExecutionArgs args)
{
Console.WriteLine(Environment.NewLine);
Console.WriteLine("Entering [ {0} ] ...", args.Method);
base.OnEntry(args);
}
public override void OnExit(MethodExecutionArgs args)
{
Console.WriteLine("Leaving [ {0} ] ...", args.Method);
base.OnExit(args);
}
}
Exception
aspect is a little different as it inherits from the OnExceptionAspect
class and must override OnException
to respond to every exception thrown by the Joinpoints
.
[Serializable]
public class ExceptionAspect : OnExceptionAspect
{
public override void OnException(MethodExecutionArgs args)
{
Console.WriteLine(String.Format("Exception in :[{0}] ,
Message:[{1}]", args.Method, args.Exception.Message));
args.FlowBehavior = FlowBehavior.Continue;
base.OnException(args);
}
}
Here is a sample of using ExceptionAspect
in our application:
[ExceptionAspect]
[LogAspect]
static void Calc()
{
throw new DivideByZeroException("A Math Error Occurred...");
}
RunInTransactionAspect
aspect inherits from the OnMethodBoundaryAspect
but in order to support Transaction management, we have to implement OnEntry
, <code><code>On
Exit, OnSuccess
, and OnException
. When an exception is thrown by a JointPoint
, the whole transaction will be rolled back and potential data problems averted. otherwise the transaction will be completed. We need to handle this situations in OnException
, and OnSuccess
methods respectively.
[Serializable]
[AspectTypeDependency(AspectDependencyAction.Order,
AspectDependencyPosition.After, typeof(LogAspect))]
public class RunInTransactionAspect : OnMethodBoundaryAspect
{
[NonSerialized]
TransactionScope TransactionScope;
public override void OnEntry(MethodExecutionArgs args)
{
this.TransactionScope = new TransactionScope(TransactionScopeOption.RequiresNew);
}
public override void OnSuccess(MethodExecutionArgs args)
{
this.TransactionScope.Complete();
}
public override void OnException(MethodExecutionArgs args)
{
args.FlowBehavior = FlowBehavior.Continue;
Transaction.Current.Rollback();
Console.WriteLine("Transaction Was Unsuccessful!");
}
public override void OnExit(MethodExecutionArgs args)
{
this.TransactionScope.Dispose();
}
}
Note: In the above code, AspectTypeDependency
attribute has been used to inform PostSharp to run RunInTransactionAspect
after LogAspect
everywhere they are used together.
Here is our main code and the result of its execution:
class Program
{
static void Main(string[] args)
{
Calc();
LongRunningCalc();
AddRecord("Reza", "Ahmadi", "r_ahmadi_1983@yahoo.com");
AddRecord("Reza", "Ahmadi", "rahmadey@gmail.com");
AddRecord("X", "Y", "r_ahmadi_1983@yahoo.com");
Console.WriteLine("Press any key to continue");
Console.ReadKey();
}
[ExceptionAspect]
[LogAspect]
static void Calc()
{
throw new DivideByZeroException("A Math Error Occurred...");
}
[LogAspect]
[TimingAspect]
static void LongRunningCalc()
{
Thread.Sleep(1000);
}
[RunInTransactionAspect]
[LogAspect]
static void AddRecord(string firstName, string lastName, string email)
{
using (var cn = new SqlConnection
(ConfigurationManager.ConnectionStrings["TestConnectionString"].ConnectionString))
{
using (var command = cn.CreateCommand())
{
if (cn.State != ConnectionState.Open) cn.Open();
try
{
command.CommandText =
"insert into person values(@f,@l) select @@identity";
command.Parameters.AddWithValue("@f", firstName);
command.Parameters.AddWithValue("@l", lastName);
var personId = command.ExecuteScalar();
command.CommandText = "insert into emailAddress values(@p,@e)";
command.Parameters.AddWithValue("@p", personId);
command.Parameters.AddWithValue("@e", email);
command.ExecuteNonQuery();
}
catch (Exception ex)
{
Trace.WriteLine("Exception during person-saving transaction: " +
ex.ToString());
throw;
}
}
}
}
}
Note: In the above code, commented codes are codes which are omitted as they have been replaced with aspects.
Conclusion
PostSharp is one of the most popular means of implementing AOP within the .NET Framework. When applied appropriately, PostSharp Aspects can reduce code clutter and help to maintain architectural standards and practices without overcomplicating the code with intermingled responsibilities. In this example, PostSharp has allowed the refactoring of one application so that:
- Logging code has been extracted into a separate Aspect and removed from the code explicitly
- Performance-analysis code has been extracted into an Aspect and is no longer intermingled
- Transactional processing has been extracted and removed from the code, cleaning up database execution code
History
- February, 2012: Initial post