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:
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");
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
{
CustomerData data = new CustomerData();
if (result == true)
{
Logger.Writer.Write("Successfully inserted customer data in "
+ (DateTime.Now-start).TotalSeconds + " seconds");
}
return result;
}
catch
{
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:
[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.
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:
[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.
[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.
public class AspectF
{
public Action<Action> Chain = null;
[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.
[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:
[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.
[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.
[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.
[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;
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
.
[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.