Click here to Skip to main content
15,860,972 members
Articles / Programming Languages / C#

AspectF Fluent Way to Add Aspects for Cleaner Maintainable Code

Rate me:
Please Sign up or sign in to vote.
4.97/5 (62 votes)
11 Jun 2011CPOL7 min read 128.1K   114   25
AspectF is a simple way to do Aspect Oriented Programming style coding which makes your code a lot cleaner and more maintainable.

Introduction

AspectF is a fluent and simple way to add Aspects to your code. If you are familiar with Aspect Oriented Programming (AOP), you know it can make your code cleaner and more maintainable. But AOP requires third party frameworks or hardcore .NET features like IL manipulation to make it work. Although the benefit outweighs the complexity, I was looking for a simpler way to do AOP style coding in order to keep my code clean and simple, moving aspects out of the way. This resulted in AspectF.

The code is available in Google code.

Background - Aspect Oriented Programming (AOP)

Aspects are common features that you write every now and then in different parts of your project. It can be some specific way of handling exceptions in your code, or logging method calls, or timing execution of methods, or retrying some methods and so on. If you are not doing it using any Aspect Oriented Programming framework, then you are repeating a lot of similar code throughout the project, which is making your code hard to maintain. For example, say you have a business layer where methods need to be logged, errors need to be handled in a certain way, execution needs to be timed, database operations need to be retried and so on. So, you write code like this:

C#
public bool InsertCustomer(string firstName, string lastName, int age, 
    Dictionary<string, string> attributes)
{
    if (string.IsNullOrEmpty(firstName)) 
        throw new ApplicationException("first name cannot be empty");
    if (string.IsNullOrEmpty(lastName))
        throw new ApplicationException("last name cannot be empty");
    if (age < 0)
        throw new ApplicationException("Age must be non-zero");
    if (null == attributes)
        throw new ApplicationException("Attributes must not be null");
    
    // Log customer inserts and time the execution
    Logger.Writer.WriteLine("Inserting customer data...");
    DateTime start = DateTime.Now;
    
    try
    {
        CustomerData data = new CustomerData();
        bool result = data.Insert(firstName, lastName, age, attributes);
        if (result == true)
        {
            Logger.Writer.Write("Successfully inserted customer data in " 
                + (DateTime.Now-start).TotalSeconds + " seconds");
        }
        return result;
    }
    catch (Exception x)
    {
        // Try once more, may be it was a network blip or some temporary downtime
        try
        {
            CustomerData data = new CustomerData();
            if (result == true)
            {
                Logger.Writer.Write("Successfully inserted customer data in " 
                    + (DateTime.Now-start).TotalSeconds + " seconds");
            }
            return result;
        }
        catch 
        {
            // Failed on retry, safe to assume permanent failure.
            // Log the exceptions produced
            Exception current = x;
            int indent = 0;
            while (current != null)
            {
                string message = new string(Enumerable.Repeat('\t', indent).ToArray())
                    + current.Message;
                Debug.WriteLine(message);
                Logger.Writer.WriteLine(message);
                current = current.InnerException;
                indent++;
            }
            Debug.WriteLine(x.StackTrace);
            Logger.Writer.WriteLine(x.StackTrace);
            return false;
        }
    }
} 

Here you see the two lines of real code, which inserts a Customer calling a class named CustomerData, is hardly visible due to all the concerns (log, retry, exception handling, timing) you have to implement in the business layer. There’s validation, error handling, caching, logging, timing, auditing, retrying, dependency resolving and what not in business layers nowadays. The more a project matures, the more concerns get into your codebase. So, you keep copying and pasting boilerplate codes and write the tiny amount of real stuff somewhere inside that boilerplate. What’s worse, you have to do this for every business layer method. Say now you want to add a UpdateCustomer method in your business layer. You have to copy all the concerns again and put the two lines of real code somewhere inside that boilerplate.

Think of a scenario where you have to make a project wide change on how errors are handled. You have to go through all the hundreds of business layer functions you wrote and change it one by one. Say you need to change the way you time execution. You have to go through hundreds of functions again and do that.

Aspect Oriented Programming solves these challenges. When you are doing AOP, you do it the cool way:

C#
[EnsureNonNullParameters]
[Log]
[TimeExecution]
[RetryOnceOnFailure]
public void InsertCustomerTheCoolway(string firstName, string lastName, int age,
    Dictionary<string, string> attributes)
{
    CustomerData data = new CustomerData();
    data.Insert(firstName, lastName, age, attributes);
}

Here you have separated the common stuff like logging, timing, retrying, validation, which are formally called ‘concern’, completely out of your real code. The method is nice and clean, to the point. All the concerns are taken out of the code of the function and added to the function using Attribute. Each Attribute here represents one Aspect. For example, you can add Logging aspect to any function just by adding the Log attribute. Whatever AOP framework you use, the framework ensures the Aspects are weaved into the code either at build time or at runtime.

There are AOP frameworks which allow you to weave the Aspects at compile time by using post build events and IL manipulations, e.g. PostSharp, some do it at runtime using DynamicProxy and some require your classes to inherit from ContextBoundObject in order to support Aspects using C# built-in features. All of these have some barrier to entry, you have to justify using some external library, do enough performance tests to make sure the libraries scale and so on. What you need is a very simple way to achieve “separation of concern”, may not be full blown Aspect Oriented Programming. Remember, the purpose here is separation of concern and to keep code nice and clean.

How AspectF Makes it Dead Simple

So, let me show you a simple way for achieving separation of concern, writing standard C# code, no Attribute or IL manipulation black magics, simple calls to classes and delegates, yet achieve nice separation of concern in a reusable and maintainable way. Best of all, it’s light, just one small class.

C#
 public void InsertCustomerTheEasyWay(string firstName, string lastName, int age,
    Dictionary<string, string> attributes)
{
    AspectF.Define
        .Log(Logger.Writer, "Inserting customer the easy way")
        .HowLong(Logger.Writer, "Starting customer insert", 
		"Inserted customer in {1} seconds")
        .Retry()
        .Do(() =>
            {
                CustomerData data = new CustomerData();
                data.Insert(firstName, lastName, age, attributes);
            });
}

Let’s see the differences with other common AOP frameworks:

  • Instead of defining Aspects outside the method, you defined them immediately inside the method.
  • Instead of making Aspects as classes, you make them as methods.

Now look at the advantages:

  • No black magic required (Attributes, ContextBoundObject, Post build event, IL Manipulation, DynamicProxy)
  • No performance overhead for the black magic
  • Order the aspects exactly the way you like. For example, you can Log once but Retry several times, or you can retry the logging as well by just moving the Retry call upward in the chain. Makes sense? No, keep reading.
  • You can pass parameters, local variables, etc. to Aspects. You can't do that using any other framework.
  • It’s not a full flown framework, just one simple class named AspectF.
  • You can define Aspects anywhere inside the code. For example, you can wrap a for loop with Aspects.

Let’s see how simple it is to build Aspects using this approach. Aspects are defined as Extension Methods. The AspectExtensions class contain all the pre-built aspects like Log, Retry, TrapLog, TrapLogThrow, etc. For instance, Here’s how the Retry works:

C#
[DebuggerStepThrough]
public static AspectF Retry(this AspectF aspects)
{
    return aspects.Combine((work) => 
        Retry(1000, 1, (error) => DoNothing(error), DoNothing, work));
}

[DebuggerStepThrough]
public static void Retry(int retryDuration, int retryCount, 
    Action<Exception> errorHandler, Action retryFailed, Action work)
{
    do
    {
        try
        {
            work();
        }
        catch (Exception x)
        {
            errorHandler(x);
            System.Threading.Thread.Sleep(retryDuration);
        }
    } while (retryCount-- > 0);
    retryFailed();
}

This Aspect calls your code as many times as you like when there’s an exception. It’s handy to wrap Database, File IO, Network IO, Webservice calls in Retry aspect because they fail due to various temporary infrastructure problems and sometimes retrying them once solves the problem. I have a habit of always retrying database insert, update, delete, calling webservice methods, dealing with files. It has saved me from many production problems.

Here’s how it works, it creates a composition of Delegate. The end result is similar to the following code:

Log(() =>
{
    HowLong(() =>
    {
        Retry(() =>
        {
            Do(() =>
            {
                CustomerData data = new CustomerData();
                data.Insert(firstName, lastName, age, attributes);
            });
        });
    });
});

The AspectF class is nothing but a fluent way to compose such code.

Here’s how you create your own aspects. First you create an extension method for the AspectF class. Say we are creating the Log aspect.

C#
[DebuggerStepThrough]
public static AspectF Log(this AspectF aspect, TextWriter logWriter, 
            string beforeMessage, string afterMessage)
{
    return aspect.Combine((work) =>
    {
        logWriter.Write(DateTime.Now.ToUniversalTime().ToString());
        logWriter.Write('\t');
        logWriter.Write(beforeMessage);
        logWriter.Write(Environment.NewLine);

        work();

        logWriter.Write(DateTime.Now.ToUniversalTime().ToString());
        logWriter.Write('\t');
        logWriter.Write(afterMessage);
        logWriter.Write(Environment.NewLine);
    });
}

You call the Combine function of AspectF to compose a delegate which then puts it inside the chain of delegates that gets fired when you finally call the Do method.

C#
public class AspectF
{
    /// <summary>
    /// Chain of aspects to invoke
    /// </summary>
    public Action<Action> Chain = null;
    /// <summary>
    /// Create a composition of function e.g. f(g(x))
    /// </summary>
    /// <param name="newAspectDelegate">A delegate that offers an aspect's behavior. 
    /// It's added into the aspect chain</param>
    /// <returns></returns>
    [DebuggerStepThrough]
    public AspectF Combine(Action<Action> newAspectDelegate)
    {
        if (this.Chain == null)
        {
            this.Chain = newAspectDelegate;
        }
        else
        {
            Action<Action> existingChain = this.Chain;
            Action<Action> callAnother = (work) => 
                existingChain(() => newAspectDelegate(work));
            this.Chain = callAnother;
        }
        return this;
    }

Here the Combine function is taking the delegate passed by the Aspect extension method, e.g. Log, and then it puts it inside the previously added Aspects so that the first Aspect calls the second one, the second one calls the third one and the final aspect calls the real code that you want to be executed inside Aspects.

The Do/Return function does the final execution.

C#
/// <summary>
/// Execute your real code applying the aspects over it
/// </summary>
/// <param name="work">The actual code that needs to be run</param>
[DebuggerStepThrough]
public void Do(Action work)
{
    if (this.Chain == null)
    {
        work();
    }
    else
    {
        this.Chain(work);
    }
}

That’s it, you now have a dead simple way of separating concerns and enjoy the AOP style programming using basic C# language features.

The AspectF class comes with several other handy aspects. For example, the MustBeNonNull aspect can be used to ensure the passed parameters are non-null. Here’s a unit test that shows how it is used:

C#
[Fact]
public void TestMustBeNonNullWithValidParameters()
{
    bool result = false;
    Assert.DoesNotThrow(delegate
    {
      AspectF.Define
        .MustBeNonNull(1, DateTime.Now, string.Empty, "Hello", new object())
        .Do(delegate
        {
            result = true;
        });
    });
    Assert.True(result, "Assert.MustBeNonNull did not call the function
        although all parameters were non-null");
}

Similarly there’s MustBeNonDefault which checks if the passed parameter values are not default value, for example, 0.

C#
[Fact]
public void TestMustBeNonDefaultWithInvalidParameters()
{
    bool result = false;

    Assert.Throws(typeof(ArgumentException), delegate
    {
        AspectF.Define
            .MustBeNonDefault<int>(default(int))
	   .MustBeNonDefault<DateTime>(default(DateTime))
            .MustBeNonDefault<string>(default(string), "Hello")
            .Do(() =>
            {
                result = true;
            });

        Assert.True(result, "Assert.MustBeNonDefault must 
		not call the function when there's a null parameter");
    });
}

There’s a Delay aspect that will execute code after a certain duration. Handy for throttling calls to certain webservice.

C#
[Fact]
public void TestDelay()
{
    DateTime start = DateTime.Now;
    DateTime end = DateTime.Now;
    AspectF.Define.Delay(5000).Do(() => { end = DateTime.Now; });
    TimeSpan delay = end - start;
    Assert.InRange<double>(delay.TotalSeconds, 4.9d, 5.1d);
}

Another useful aspect is RunAsync which calls the code asynchronously. Sometimes you have webservice methods that need to return immediately and do the main job asynchronously. You can just wrap the method’s code using RunAsync and the code inside it will run asynchronously.

C#
[Fact]
public void TestAspectAsync()
{
    bool callExecutedImmediately = false;
    bool callbackFired = false;
        AspectF.Define.RunAsync().Do(() =>
    {
        callbackFired = true;
        Assert.True(callExecutedImmediately, 
            "Aspect.RunAsync Call did not execute asynchronously");
    });
    callExecutedImmediately = true;
    // wait until the async function completes
    while (!callbackFired) Thread.Sleep(100);
    bool callCompleted = false;
    bool callReturnedImmediately = false;
    AspectF.Define.RunAsync(() => 
        Assert.True(callCompleted, 
            "Aspect.RunAsync Callback did not fire
             after the call has completed properly"))
        .Do(() =>
        {
            callCompleted = true;
            Assert.True(callReturnedImmediately,
               "Aspect.RunAsync call did not run asynchronously");
        });
    callReturnedImmediately = true;
    while (!callCompleted) Thread.Sleep(100);
}

Another handy Aspect is TrapLog, which captures the exceptions raised and logs it. It does not let the exception propagate. If you want the exception to be logged and then thrown again so that is propagates, you can use TrapLogThrow.

C#
[Fact]
public void TestTrapLog()
{
    StringBuilder buffer = new StringBuilder();
    StringWriter writer = new StringWriter(buffer);
    Assert.DoesNotThrow(() =>
    {
        AspectF.Define.TrapLog(writer).Do(() =>
        {
            throw new ApplicationException("Parent Exception",
                new ApplicationException("Child Exception",
                    new ApplicationException("Grandchild Exception")));
        });
    });
    string logOutput = buffer.ToString();
    Assert.True(logOutput.Contains("Parent Exception"));
    Assert.True(logOutput.Contains("Child Exception"));
    Assert.True(logOutput.Contains("Grandchild Exception"));
}

So, there you have it... a nice clean way of doing separation of concern and have the taste of Aspect Oriented Programming without any heavyweight libraries!

Conclusion

AOP purists must be boiling inside - "This is not AOP at all, you moron!" Well, AOP is all about separation of concern and cross cutting. It's true AspectF does not show you a way to do cross-cutting, but it does show you a way to achieve separation of concern. Just because AOP shows you a way to write code outside method boundary does not mean you could not do the same inside method boundaries. For example, what if in C# you had to declare Attribute inside the method body? Then there would be little difference in doing AOP using Attribute or using AspectF instead. The primary goals of AspectF are - no dependency on external library or non-standard .NET features, little effort to get new Aspects created, use strong typing and build features while using Aspects.

License

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


Written By
Architect BT, UK (ex British Telecom)
United Kingdom United Kingdom

Comments and Discussions

 
Questionperfect, help me a lot Pin
scy27875351722-Jan-13 16:26
scy27875351722-Jan-13 16:26 
SuggestionGeneric overrides Pin
Daniele Rota Nodari12-Nov-12 1:41
Daniele Rota Nodari12-Nov-12 1:41 
GeneralRe: Generic overrides Pin
Omar Al Zabir12-Nov-12 1:47
Omar Al Zabir12-Nov-12 1:47 
GeneralMy vote of 5 Pin
Matthew Searles30-Jul-12 23:39
Matthew Searles30-Jul-12 23:39 
GeneralCool, thanks Pin
Jeff Circeo29-Apr-12 3:59
Jeff Circeo29-Apr-12 3:59 
GeneralMy vote of 5 Pin
Sunasara Imdadhusen20-Jun-11 19:57
professionalSunasara Imdadhusen20-Jun-11 19:57 
GeneralThis isn't AOP Pin
Pete O'Hanlon12-Jun-11 9:56
subeditorPete O'Hanlon12-Jun-11 9:56 
GeneralHowLong() method Pin
wincorp5312-Jun-11 8:11
wincorp5312-Jun-11 8:11 
GeneralMy vote of 5 Pin
mbarbac18-May-11 11:10
mbarbac18-May-11 11:10 
GeneralMy vote of 5 Pin
raja_krish22-Feb-11 8:08
raja_krish22-Feb-11 8:08 
GeneralMy vote of 5 Pin
jhornb1-Dec-10 7:02
jhornb1-Dec-10 7:02 
GeneralMy vote of 5 Pin
Shahdat Hosain22-Jul-10 18:58
Shahdat Hosain22-Jul-10 18:58 
GeneralTwo other AOP support packages for .NET Pin
Your Display Name Here4-Dec-09 5:24
Your Display Name Here4-Dec-09 5:24 
GeneralThinking outside of the box [modified] Pin
Oleg Shilo26-Sep-09 22:24
Oleg Shilo26-Sep-09 22:24 
GeneralJust amazing Pin
Alomgir Miah A22-Sep-09 7:16
Alomgir Miah A22-Sep-09 7:16 
GeneralBrilliant Pin
Pierre Nocera21-Sep-09 9:19
Pierre Nocera21-Sep-09 9:19 
GeneralSweet Pin
Nicholas Butler21-Sep-09 8:51
sitebuilderNicholas Butler21-Sep-09 8:51 
QuestionPerformance? Pin
Itay Sagui20-Sep-09 21:19
Itay Sagui20-Sep-09 21:19 
AnswerRe: Performance? Pin
Omar Al Zabir20-Sep-09 23:37
Omar Al Zabir20-Sep-09 23:37 
GeneralGood Job Pin
Andrew Rissing19-Sep-09 13:09
Andrew Rissing19-Sep-09 13:09 
GeneralNice one Omar Pin
Sacha Barber19-Sep-09 7:02
Sacha Barber19-Sep-09 7:02 
GeneralDead simple - yes. But there are closures, so such a code can be noticeably slower. Pin
Alex Yakunin19-Sep-09 5:22
Alex Yakunin19-Sep-09 5:22 
GeneralRetry() loop Pin
Meixger Martin19-Sep-09 2:36
Meixger Martin19-Sep-09 2:36 
GeneralRe: Retry() loop Pin
Meixger Martin24-Sep-09 22:47
Meixger Martin24-Sep-09 22:47 
GeneralNine ... Pin
Jammer19-Sep-09 2:22
Jammer19-Sep-09 2:22 
This is very interesting stuff, thanks for a great article.

Jammer
My Blog | Articles

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.