Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / SQL

Aspect Oriented Programming Using C# and PostSharp

4.79/5 (44 votes)
13 Aug 2014CPOL5 min read 174.8K   3.7K  
This article discusses basic concepts in AOP and uses PostSharp to implement AOP concepts in a C# application.

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.

app.config needed to be config

Sql Query to create sample tables

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.

C#
void AddRecord(string firstName, string lastName, string email)
{
    Try
    {
        //Code To Add a Record...
    }
    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 Joinpoints 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:

Project Files

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:

C#
_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:

C#
[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:

C#
[TimingAspect]
static void LongRunningCalc()
{
    //wait for 1000 milliseconds
    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:

Using Aspect to report execution time

LogAspect is quite similar to TimingAspect as it inherited from the OnMethodBoundaryAspect and override OnEntry, OnExit as below:

C#
[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.

C#
[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:

C#
[ExceptionAspect]
[LogAspect]
static void Calc()
{
    throw new DivideByZeroException("A Math Error Occurred...");
}

Using Aspect to report execution time

RunInTransactionAspect aspect inherits from the OnMethodBoundaryAspect but in order to support Transaction management, we have to implement OnEntry, <code><code>OnExit, 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.

C#
[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:

C#
class Program
    {
        static void Main(string[] args)
        {
            Calc();
            LongRunningCalc();

            //Adding records using a method
            //which is run in a Transaction
            AddRecord("Reza", "Ahmadi", "r_ahmadi_1983@yahoo.com");
            AddRecord("Reza", "Ahmadi", "rahmadey@gmail.com");
            AddRecord("X", "Y", "r_ahmadi_1983@yahoo.com"); //here an Exception will be thrown

            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()
        {
            //wait for 1000 milliseconds
            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();
                    //command.Transaction = cn.BeginTransaction();

                    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();

                        //command.Transaction.Commit();
                    }
                    catch (Exception ex)
                    {
                        Trace.WriteLine("Exception during person-saving transaction: " + 
                ex.ToString());
                        //command.Transaction.Rollback();
                        throw;
                    }
                }
            }
        }
    }

Note: In the above code, commented codes are codes which are omitted as they have been replaced with aspects.

Using all 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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)