Introduction to AOP
Aspect-oriented programming (AOP) is,
according to Wikipedia, “a programming paradigm that aims to increase
modularity by allowing the separation of crosscutting concerns.” It deals with
functionality that occurs in multiple parts of the system and separates it from
the core of the application, thus improving separation of concerns while
avoiding duplication of code and coupling.
The biggest advantage of AOP is that you
only have to worry about the aspect in one place, programming it once and
applying it in all the places where needed.
There are many uses for AOP, such as:
There are many uses for AOP, such as:
- Implementing logging in your application.
- Using authentication before an operation (such as allowing some operations only for authenticated users).
- Adding caching to certain method calls.
- Global error handling.
- Changing the behavior of some methods.
In the .NET Framework, the most commonly
techniques to implement AOP are post-processing and code interception. The
former is the technique used by PostSharp (postsharp.net) and the latter is
used by dependency injection (DI) containers such as Castle DynamicProxy
and Unity’s
Interception feature. These tools usually use a design pattern named
Decorator or Proxy to perform the code interception.
In this two-part blog post I’ll take a look
on the two approaches that don’t need a DI container:
I’ll try to finish with my personal conclusions.
Part 1: Implementing a caching decorator with RealProxy class
The RealProxy class gives you basic functionality for proxies. It’s an abstract class that must be inherited by overriding its Invoke method and adding new functionality. This class is in the namespace System.Runtime.Remoting.Proxies.
Below we define a caching decorator using RealProxy.
In the constructor we pass the type of the decorated class to the base class. Next we override the Invoke method that receives an IMessage parameter. It contains a dictionary with all the parameters passed to the original method call. After extracting the MethodInfo we can add the aspect we need before calling the method.
public class CachingProxy<T>: RealProxy
{
private readonly T _decorated;
public CachingProxy(T decorated): base(typeof (T))
{
_decorated = decorated;
}
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
var methodInfo = methodCall.MethodBase as MethodInfo;
MethodResultCache cache = MethodResultCache.GetCache(methodInfo);
object result = cache.GetCachedResult(methodCall.InArgs);
if (result == null)
{
try
{
result = methodInfo.Invoke(_decorated, methodCall.InArgs);
cache.CacheCallResult(result, methodCall.InArgs);
}
catch (Exception e)
{
return new ReturnMessage(e, methodCall);
}
}
return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
}
}
In the constructor we pass the type of the decorated class to the base class. Next we override the Invoke method that receives an IMessage parameter. It contains a dictionary with all the parameters passed to the original method call. After extracting the MethodInfo we can add the aspect we need before calling the method.
The caching helper that is used has methods for getting cached results or adding results to cache. The implementation is not important for now, will be available for download.
public interface IMethodResultCache
{
object GetCachedResult(IEnumerable<object> arguments);
void CacheCallResult(object result, IEnumerable<object> arguments);
}
We will use this caching decorator on a simple repository class with the following contract:
public interface IUsersRepository
{
List<User> GetAll();
User GetById(int id);
}
The actual implementation that gets the data from the DB is also out of the scope of this article. Imagine a EntityFramework implementation or other basic data access alternative.
To use the decorated repository, we must use the GetTransparentProxy method, which will return an instance of IUsersRepository. Every method of this instance that’s called will go through the proxy’s Invoke method. To ease this process, we create a Factory class to create the proxy and return the instance for the repository:
public class UsersRepositoryFactory
{
public static IUsersRepository CreateRealProxy()
{
var repository = new UsersRepository();
var dynamicProxy = new CachingProxy<IUsersRepository>(repository);
return dynamicProxy.GetTransparentProxy() as IUsersRepository;
}
}
This is how the calling code would look like:
[TestMethod]
public void TestRealProxy()
{
IUsersRepository repository = UsersRepositoryFactory.CreateRealProxy();
repository.GetById(1);
repository.GetById(1);
repository.GetById(2)
}
And these are the test results for a run, the messages are writen to output by the caching helper's GetCachedResult method:
Instead of using the factory we can imagine a different scenario of using annotations to mark cacheable classes/members and linking them to the caching proxy. This approach is used by Unity's interception feature.
Part 2: to be continued
Source code link.
Happy coding.
No comments:
Post a Comment