In the following lines we will talk about AOP and how we can implement our own AOP stack using .NET Core features. First of all, let us see what AOP means. The acronym comes from Aspect Oriented Programming and it is another programming paradigm with the main goal of increasing the modularity of an application. AOP tries to achieve this goal by allowing separation of cross-cutting concerns.
Each part of the application is broken down into distinct units based on the functionality (concerns). Of course even if we don"t use this paradigm we will try to have a clear separation of functionalities. But in AOP all the functionalities are separated, even the one that in OOP we accept to be cut crossing.
A good example for this case is the audit and logging. Normally, if we are using OOP to develop an application that needs logging or audit we will have in one form or another different calls to our logging mechanism in our code. In OOP this can be accepted, because this is the only way to write logs, to do profiling and so on.
When we are using AOP, the implementation of the logging or audit system will need to be in a separate module. Not only this, but we will need a way to write the logging information without writing code in other modules that make the call itself to the logging system.
There are different options that can be used in AOP to resolve this problem. It depends on what kind of technology you are using, what kind of stack you want to use and so on. We could use attributes (annotation) of methods and classes that would activate logging or audit. Another approach is to configure proxies that would be used by the system as the real implementation of a functionality. This proxy would be able to write the logs and make the call of the base implementation.
Almost all the frameworks that are available for AOP are around interception. Using interception, developers can specify what methods need to be intercepted and what should happen in this case. By interception we can even add new fields, properties, methods or even change the current implementation.
Usually there are two ways offered by different frameworks that offer AOP support (at runtime and at compile time).
When the framework offers AOP support at runtime, it means that in real time it will create different proxies that will redirect our calls. This will give us a lot of flexibility, being able to change at runtime the behavior of our application.
Another approach is at compile time. This type of frameworks are usually integrated with our IDE and development environment. At compile time, they will change our code and insert the calls to our different features. In the end, we will end up with the same code as we would call the code from our method, but the code that we need to maintain is simpler, clean and with all the concerns clearly separated.
Of course all this comes with a cost that normally can be felt especially in two places - time and debugging.
When the AOP framework uses proxies at real time, the performance of our application can decrease. This is happening because there is a man in the middle that intercepts our calls and takes some actions. At this level usually reflection is used and all of us know that reflections cost a lot from the processor perspective.
Having a code that changes at compile time means that we can have some problems when we need to debug our code and find an issue. This is happening because at runtime you don"t end up with only your code, you end up with your original code from the base concern plus the code from the second concert that you needed in the base concern and with the AOP framework code that make the redirection.
As we can see in the above diagram, only the first and the last steps are part of the normal flow. The rest of them are added by the AOP framework.
A join point is represented by the point in the code where we need to run our advice. This is a point in the code where you can make a call to another feature in a very simple way. In general frameworks that support AOP use methods, classes and properties are their main join points.
For example before and/or during calling a method you can execute your custom code. The same thing can happen for classes and properties. In general you will discover that the concepts of AOP are very simple, but pretty hard to implement keeping a clean and simple code.
Define a way to specify a join point in your system. It gives the developer the possibility to specify and identify a join point in the system where we want to make a specific call. This can be accomplished in different ways, from attributes (annotation) to different configuration (in files, in code and many more).
Advice is referring to the code that we want to execute when a join point is reached. Basically this is represented by the code that is called around a join point.
For example, before a method is called, we want to write some information to the trace.
Aspect is formed from two different items - the pointcut and the advice. This combination of these two items forms an aspect. In general, when we want to use AOP we have a location where we want to execute the code and the custom code itself that we want to be run.
There are different ways to implement AOP in .NET. On the market, you will find a lot of frameworks that offer this support, a part of them are free.
Unity provides support for AOP in some part of the application. In general, Unity offers support for the most common scenarios like exception handling, logging, security or data access.
This is one of the most known frameworks in .NET world for AOP. This is not a free framework, but it is full with useful features and 100% integrated with development environment.
This is a free tool that can be found on Codeplex. It offers the base features needed to develop an application using AOP paradigm.
This library offers also support for AOP, offering different capabilities like authorization, exception handling, validation, logging and performance counter. It is pretty similar to the features that are offered with Unity.
This is another stack similar to Aspect.NET. In both cases, you should know that you have access only to the base features of AOP paradigm.
This is another stack similar to Aspect.NET. In both cases, you should know that you have access only to the base features of AOP paradigm.
From my perspective, the tool that offers all the features that are needed when you want to use AOP is PostSharp. But, based on your needs you should always try to identify the tool that satisfies your needs.
The good news is that we can use AOP without any kind of tool. It may be more complicated, but what you can accomplish using .NET API is pretty interesting. In the following part of the article we will see how we can use RealProxy class to intercept method calls and inject custom behavior. RealProxy class can be found in .NET Core stack.
The most important method of RealProxy is "Invoke". This method is called each time a method from your specific class is called. From it you can access the method name, parameters and call your real method or a fake one.
Before saying "Wooww, it"s so cool!" you should know that this will work only when you use also the interfaces.
In the following example we will see how we can implement a custom profiling mechanism using RealProxy class.
The first step is to create a custom attribute, which accepts a custom message that will be written when we write the duration time to the trace.
public class DurationProfillingAttribute : Attribute
{
public DurationProfillingAttribute(string message)
{
Message = message;
}
public DurationProfillingAttribute()
{
Message = string.Empty;
}
public string Message { get; set; }
}
Next we need a generic class that extends RealProxy and calculates the duration of the call. In the Invoke method we will need to use a Stopwatch that will calculate how long a call takes. At this level we can check if a specific method is decorated with our attribute.
public class DurationProfilingDynamicProxy : RealProxy
{
private readonly T _decorated;
public DurationProfilingDynamicProxy(T decorated)
: base(typeof(T))
{
_decorated = decorated;
}
public override IMessage Invoke(IMessage msg)
{
IMethodCallMessage methodCall =
(IMethodCallMessage)msg;
MethodInfo methodInfo = methodCall.MethodBase as
MethodInfo;
DurationProfillingAttribute profillingAttribute =
(DurationProfillingAttribute)methodInfo.
GetCustomAttributes(typeof(
DurationProfillingAttribute)).FirstOrDefault();
// Method don"t needs to be measured.
if (profillingAttribute == null)
{
return NormalInvoke(methodInfo, methodCall);
}
return ProfiledInvoke(methodInfo, methodCall,
profillingAttribute.Message);
}
private IMessage ProfiledInvoke(MethodInfo methodInfo, IMethodCallMessage methodCall, string profiledMessage)
{
Stopwatch stopWatch = null;
try
{
stopWatch = Stopwatch.StartNew();
var result = InvokeMethod(methodInfo, methodCall);
stopWatch.Stop();
WriteMessage(profiledMessage,
methodInfo.DeclaringType.FullName,
methodInfo.Name, stopWatch.Elapsed);
return new ReturnMessage(result, null, 0,
methodCall.LogicalCallContext,
methodCall);
}
catch (Exception e)
{
if (stopWatch != null
&& stopWatch.IsRunning)
{
stopWatch.Stop();
}
return new ReturnMessage(e, methodCall);
}
}
private IMessage NormalInvoke(MethodInfo methodInfo,
IMethodCallMessage methodCall)
{
try
{
var result = InvokeMethod(methodInfo, methodCall);
return new ReturnMessage(result, null, 0,
methodCall.LogicalCallContext, methodCall);
}
catch (Exception e)
{
return new ReturnMessage(e, methodCall);
}
}
private object InvokeMethod(MethodInfo methodInfo,
IMethodCallMessage methodCall)
{
object result = methodInfo.Invoke(_decorated,
methodCall.InArgs);
return result;
}
private void WriteMessage(string message, string
className, string methodName,
TimeSpan elapsedTime)
{
Trace.WriteLine(string.Format("
Duration Profiling: "{0}" for "{1}.{2}"
Duration:"{3}"", message,
className,methodName, elapsedTime));
}
We could have another approach here, calculating the duration for all the methods from the class. You can find below the classes used to test the implementation. Using "GetTransparentProxy" method we can obtain a reference to our interface.
class Program
{
static void Main(string[] args)
{
DurationProfilingDynamicProxy
fooDurationProfiling = new
DurationProfilingDynamicProxy(new Foo());
IFoo foo = (IFoo)fooDurationProfiling.
GetTransparentProxy();
foo.GetCurrentTime();
foo.Concat("A", "B");
foo.LongRunning();
foo.NoProfiling();
}
}
public interface IFoo
{
[DurationProfilling("Some text")]
DateTime GetCurrentTime();
[DurationProfilling]
string Concat(string a, string b);
[DurationProfilling("After 2 seconds")]
void LongRunning();
string NoProfiling();
}
public class Foo : IFoo
{
public DateTime GetCurrentTime()
{
return DateTime.UtcNow;
}
public string Concat(string a, string b)
{
return a + b;
}
public void LongRunning()
{
Thread.Sleep(TimeSpan.FromSeconds(2));
}
public string NoProfiling()
{
return "NoProfiling";
}
}
In conclusion we can say that AOP can make our life easier. This doesn"t mean that now we will use it in all the projects. AOP should be used only where it makes sense and usually it can be very useful when we are working on a project that is very complicated with a lot of modules and functionality.
In the next article we will discover how we can use Unity to implement AOP.