Click here to Skip to main content
Click here to Skip to main content

Aspect Oriented Programming Using C# and PostSharp

By , 29 Feb 2012
 

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 the use of 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 amount of code and create codes 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 code for 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 codes. 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
    {
        //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 above 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:

_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 above 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 above every Aspect class.

Here I've used TimingAspect aspect as an attribute:

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

[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...");
}

Using Aspect to report execution time

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

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

            //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)

About the Author

Reza Ahmadi
Software Developer (Senior) DPKhttps://www.codeproject.com/script/Membership/M
Finland Finland
Member
I have worked in many companies holding different positions like Software Developer, Software Architect and so on.
 
I love coding with .NET, and I love to apply new techniques and technologies into my work.
 
Currently studying Ms. Software Development in the UTA (Finland) and working as a Web Application Developer.
 
Certificates:
MCTS (Microsoft Technology Specialist)
MCPD (Microsoft Professional Developer)

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Questionno resultsmemberMachacaz10 Jan '13 - 6:22 
when i run you solution/project i get no log in console -
any idea why?
AnswerRe: no resultsmemberReza Ahmadi10 Jan '13 - 9:41 
Hi,
Well one guess is that it actually depends how you run your application. On debug mode or on release mode as Trace object is meaningful mostly in release mode. Take a look at your Output window, for sure there must be something at least.
 
I hope it helps
I am not what happened to me, I am what I choose to become.

QuestionWhat about the try and using blocksmemberkelvin199726 Nov '12 - 12:14 
At the end of the day, it will be nice to handle connection exception and/or closing in the aspect. Is it possible to use postsharp to handle the try and using blocks?
AnswerRe: What about the try and using blocksmemberReza Ahmadi11 Jan '13 - 22:49 
Hi,
Well, in terms of Try/Catch blocks you can use OnExceptionAspect to implement them as there is a simple one in the sample project. For a more detailed one please read this:
http://www.rhyous.com/2012/06/15/aop-implementing-try-catch-in-csharp-with-postsharp/[^]
 
About using blocks I am not sure.
I am not what happened to me, I am what I choose to become.

GeneralMy vote of 5membermbsmbs21 Oct '12 - 7:46 
Hi & thanks (persian : Salam va Mamnun az poste kHoobetun !! man ye moshkel daram moghye Compile in Error ro migiram : Error connecting to the pipe server. See previous warnings for details. Lotfan Rahnamayeem konid (mbs220@gmail.com) Thanks)
GeneralRe: My vote of 5memberReza Ahmadi21 Oct '12 - 8:08 
Hi, it is my pleasure you liked it. Actually, I have not encountered such a problem. However, I googled and I found this thread which is related to Postsharp(obviously Postsharp is making this problem):
http://www.sharpcrafters.com/forum/Topic6135-20-1.aspx[^]
Here guys with similar problem have come up with some solutions
 
I hope it helps,
Cheers
I am not what happened to me, I am what I choose to become.

GeneralRe: My vote of 5membermbsmbs21 Oct '12 - 8:20 
i watched it before But was not my solution!!
Cry | :((
Anyway thanks a lot .
GeneralRe: My vote of 5memberReza Ahmadi21 Oct '12 - 8:30 
Maybe reinstalling Postsharp can help or maybe clearing the project and then compiling again?
I am not what happened to me, I am what I choose to become.

GeneralRe: My vote of 5membermbsmbs21 Oct '12 - 9:33 
i change the [Disable PostSharp] to [YES] in Project's Property & i Compile it Successfully Thumbs Up | :thumbsup: .
i'm looking for a better Solution to compile my project with PostSharp
if not found , I'll Reinstall it . thanks ManRose | [Rose]
GeneralRe: My vote of 5memberReza Ahmadi21 Oct '12 - 9:36 
Great!
I am not what happened to me, I am what I choose to become.

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

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 29 Feb 2012
Article Copyright 2012 by Reza Ahmadi
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid