Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / SQL

Aspect Oriented Programming Using C# and PostSharp

Rate me:
Please Sign up or sign in to vote.
4.79/5 (44 votes)
13 Aug 2014CPOL5 min read 170.6K   3.7K   104   27
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)


Written By
Software Developer (Senior) DPKhttps://www.codeproject.com/script/Membership/M
Canada Canada
I am programmer and I love coding using C#, C++, and JavaScript. Smile | :)

Comments and Discussions

 
Bugdoesnt work Pin
zekikaya18-Apr-18 20:51
zekikaya18-Apr-18 20:51 
QuestionPostSharp is not free? Is it compatible with Xamarin.Forms Pin
oniDino10-Nov-17 6:21
oniDino10-Nov-17 6:21 
QuestionCode not injecting Pin
RickNash17-Aug-16 0:34
RickNash17-Aug-16 0:34 
Questionit is good but need your help to make this prog wokring Pin
kamal_mca11-May-15 5:53
kamal_mca11-May-15 5:53 
QuestionGetting Unhandled Exception on running console app Pin
asarkar9917-Oct-14 4:14
asarkar9917-Oct-14 4:14 
using VS 2010, it is not hitting any of the breakpoints on any of the aspect classes and instead just ignoring them. Have installed Postsharp on PC. Any ideas?
AnswerRe: Getting Unhandled Exception on running console app Pin
Reza Ahmadi21-Oct-14 6:39
Reza Ahmadi21-Oct-14 6:39 
QuestionNot working Pin
abhrcome2-Sep-14 0:13
abhrcome2-Sep-14 0:13 
AnswerRe: Not working Pin
Reza Ahmadi9-Sep-14 22:14
Reza Ahmadi9-Sep-14 22:14 
AnswerRe: Not working Pin
Member 1167189020-Aug-15 1:45
Member 1167189020-Aug-15 1:45 
QuestionMy vote of 5 Pin
Emre Ataseven2-Jun-14 4:24
professionalEmre Ataseven2-Jun-14 4:24 
AnswerRe: My vote of 5 Pin
Reza Ahmadi2-Jun-14 23:49
Reza Ahmadi2-Jun-14 23:49 
QuestionRunning in VS 2012 Pin
diogoborges1027-May-14 1:06
diogoborges1027-May-14 1:06 
AnswerRe: Running in VS 2012 Pin
Reza Ahmadi28-May-14 10:01
Reza Ahmadi28-May-14 10:01 
GeneralMy vote of 5 Pin
Veljko Zrnic10-Jun-13 22:59
Veljko Zrnic10-Jun-13 22:59 
GeneralRe: My vote of 5 Pin
Reza Ahmadi11-Jun-13 1:05
Reza Ahmadi11-Jun-13 1:05 
Questionno results Pin
Machacaz10-Jan-13 6:22
Machacaz10-Jan-13 6:22 
AnswerRe: no results Pin
Reza Ahmadi10-Jan-13 9:41
Reza Ahmadi10-Jan-13 9:41 
QuestionWhat about the try and using blocks Pin
kelvin199726-Nov-12 12:14
kelvin199726-Nov-12 12:14 
AnswerRe: What about the try and using blocks Pin
Reza Ahmadi11-Jan-13 22:49
Reza Ahmadi11-Jan-13 22:49 
GeneralMy vote of 5 Pin
MB Seifollahi21-Oct-12 7:46
professionalMB Seifollahi21-Oct-12 7:46 
GeneralRe: My vote of 5 Pin
Reza Ahmadi21-Oct-12 8:08
Reza Ahmadi21-Oct-12 8:08 
GeneralRe: My vote of 5 Pin
MB Seifollahi21-Oct-12 8:20
professionalMB Seifollahi21-Oct-12 8:20 
GeneralRe: My vote of 5 Pin
Reza Ahmadi21-Oct-12 8:30
Reza Ahmadi21-Oct-12 8:30 
GeneralRe: My vote of 5 Pin
MB Seifollahi21-Oct-12 9:33
professionalMB Seifollahi21-Oct-12 9:33 
GeneralRe: My vote of 5 Pin
Reza Ahmadi21-Oct-12 9:36
Reza Ahmadi21-Oct-12 9:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.