Introduction
Most AOP frameworks require compromises due to technical limitations (changes needed in the source code, changes in the compilation or deployment process). Few of them are simple to handle and the coupling with the framework is often too strong. This is one of the reasons why too few people are interested in aspect-oriented programming. However, the application of the concepts of aspect-oriented programming is very simple provided they are well understood and have the right tools. This is why I would like to share with you a basic AOP scenario using NConcern .NET, a dedicated open source framework (under MIT licence): https://github.com/Virtuoze/NConcern.
Background
The principle of Aspect-Oriented Programming consists firstly in factorizing the transerve source code in order to maintain it and to make it evolve easily without impacting the business code and, on the other hand, to remove it from the business code to improve readability.
The simplest scenario to understand is to log method calls into console. The goal is to write the business code without worrying about cross-cutting concerns (logging in this scenario). The integration between the business code and the logging aspect is then carried out using the AOP framework.
Business
For our scenario, here is a very simple code that represents a basic calculator:
static public class Calculator
{
static public int Add(int a, int b)
{
return a + b;
}
}
The calculator just has an "Add
" static
method to do an integer addition.
Program
Here is a simple usage of calculator in a console application:
static public class Program
{
static public void Main(string[] args)
{
Calculator.Add(2, 3);
}
}
The method call is not logged into console and if we want to log the method call into console. It is normally necessary (without an AOP Framework) to add the statement directly in the method to do it explicitly as below:
static public class Calculator
{
static public int Add(int a, int b)
{
try
{
return a + b;
}
finally
{
Console.WriteLine(string.Format("You called {0} method.",
MethodBase.GetCurrentMethod().Name));
}
}
}
I use try
/finally
block to ensure that the log in the console is made even in case of exception handling. MethodBase.GetCurrentMethod()
statement is required if we want to rationalize the logging code.
Logging Aspect with NConcern .NET
To use NConcern .NET, you need to reference NConcern.dll assembly. You can download it from:
It's easier to use nuget because it allows you to stay more up-to-date.
On contextual menu of your project, click on "Manage Nuget Packages...".
On NuGet Package Manager, fill the search textbox with the keyword NConcern
.
Install the package by clicking on "install" button.
After installation, you can see NConcern as reference.
Before logging aspect definition, it is necessary to define the builtin assembly attribute "DebuggableAttribute
" to disable method inlining because NConcern is based on method swapping and cannot work when inling optimization is done. Add the debuggable attribute in the "AssemblyInfo.cs" file of target assembly (where business is located) with "IsJITTrackingEnabled
" to true
and "IsJITOptimizerDisabled
" to true
too.
Now you are ready to create a logging aspect. "NConcern.IAspect
" is the interface you need to implement to define an aspect. There is only one method to implement called "Advise
" taking a "MethodInfo
" as argument and returning an "IEnumerable<Advice>
".
The method received as argument is the "target method to advise". The "Advise
" method has to return some "Advices
". An advice is an additional code to inject into a method to materialized the aspect. In our case, we have to create an advice to log the target method call into console after the target method call itself.
public class Logging
{
public IEnumerable<Advice> Advise(MethodInfo method)
{
yield return Advice.Basic.After(() => Console.WriteLine(string.Format
("You called {0} method.", method.Name)))
}
}
Advice can be defined using static
methods from "Advice.Basic
" (simple delegates), "Advice.Linq
" (linq expression) and "Advice.Reflection
" (reflection emit). At this stage, logging aspect is able to advise any method by adding a console log after method execution. But nowhere have we specified that the "Add
" method of the calculator should be managed by the logging aspect.
Weave logging aspect into "Add
" method with the "NConcern.Aspect.Weave<T>(MethodInfo)
" method:
static public class Program
{
static public void Main(string[] args)
{
Aspect.Weave<Logging>(typeof(Calculator).GetMethod("Add"));
Calculator.Add(2, 3);
}
}
Now when "Add
" method is called, a log is done in the console. "NConcern.Aspect.Weave<T>(...)
" has some overload to simplify the process. For example, we can weave logging for all methods of Calculator
like this:
static public class Program
{
static public void Main(string[] args)
{
Aspect.Weave<Logging>(method => method.DeclaringType == typeof(Calculator));
Calculator.Add(2, 3);
}
}
The lifecycle of advices can be controlled with:
Aspect.Weave<T>(...)
: weave an aspect to target method(s)
Aspect.Release<T>(...)
: release aspect for target methods(s)
Aspect.Lookup<T>(...)
: get aspect mapping
You can see that "custom attribute" is not required to implement AOP. However, it can help to discriminate method. If you want to use it to discriminate methods, you have to be carefully about good practice of custom attribute. Indeed, I am used to seeing inadequate use that involves too much coupling. For example, don't create an attribute specialized for an aspect like "LogAttribute
". Create an attribute to describe your business like "ServiceAttribute
" (to map: logging aspect manage method attributed with "ServiceAttribute
"), not to add a dependency.
Conclusion
Implementing Aspect-Oriented Programming in C# can be easy and transparent if you use a good framework. NConcern is a clear, precise and simple implementation without too much compromise. It allows to stay on the initial objective: write the business code without worrying about a cross-cutting concerns because transversal source code can be totally externalized.