C# – Introduction to Aspect Oriented Programming with RealProxy

In many complex business applications, software developers are maybe asked for making protocol of all changes on an object. For example, in a task management system, changes of a task such as dead line, attachments, comments… by users should be logged and displayed in his history. Or in a fault management system, all changes of a fault such as status, companies, workers,… should be also recorded. Who adds what, who deletes what, tracking changes… is the point of interest. How should we solve these requirements without violating our software architecture and duplicating code? We need something that can cross the boundaries of an object or a layer and Aspect Oriented Programming (AOP) is exactly what we need. According to Wikipedia,

Aspect-oriented programming entails breaking down program logic into distinct parts (so-called concerns, cohesive areas of functionality). Nearly all programming paradigms support some level of grouping and encapsulation of concerns into separate, independent entities by providing abstractions (e.g., functions, procedures, modules, classes, methods) that can be used for implementing, abstracting and composing these concerns. Some concerns “cut across” multiple abstractions in a program, and defy these forms of implementation. These concerns are called cross-cutting concerns.

In this post, I would like to make a small example to show how we can implement AOP for logging function with RealProxy class.

1. Prerequisites

I create solution with Visual Studio 2013, so you need at least a Visual Studio 2013 Community Edition to open the project. If you still don’t have any Visual Studio 2013 edition, you can get one free here http://go.microsoft.com/fwlink/?LinkId=517284

2. Demo

The demo is a typical database application where user can create, update or delete object from database. To keep things simple, I will skip the part of real CRUD actions which make real changes to a database system. All CRUD functions are dummy functions writing a message to console when they are called.

2.1 No logging

A repository of Order with some basic CRUD actions is the core of our demo. The implementation of Repository is in code listing below. As I mentioned above they are just dummy functions, no real database transaction will be made here.

internal class Repository<T> : IRepository<T>
{
	public void Add(T entity)
	{
		Console.WriteLine("Adding {0}", entity);
	}

	public void Delete(T entity)
	{
		Console.WriteLine("Deleting {0}", entity);
	}

	public void Update(T entity)
	{
		Console.WriteLine("Updating {0}", entity);
	}
}

private static void NoMethodLogging()
{
Console.WriteLine(“Start – No method logging…”);
IRepository orderRepository = new Repository();
Order order = new Order() { Id = 1, CustomerId = 1, Number = “20150317” };
orderRepository.Add(order);
orderRepository.Update(order);
orderRepository.Delete(order);
Console.WriteLine(“End – No method logging…”);
}

As we execute the code listing above, we have a normal process. Entity is created, gets updated and then deleted. Because we didn’t equip any logging function, the result looks like following image.

No logging

Now our program grows up, users want to track all changes and we are asked to install a logging function which should record when Add, Update, and Delete functions are called. A simple logging engine with help of design pattern is what we should consider first.

2.2 Decorator Design Pattern

Decorator design pattern can be our choice because this pattern allows behaviors to be added to an object, either statically or dynamically, without affecting the behavior of other objects from the same class. With this pattern we can add behaviors to Repository class and then reroute all calls to CRUD functions through our logging engine before they are executed.

internal class SimpleMethodLoggingRepository<T> : IRepository<T>
{
	private readonly IRepository<T> decorated;

	public SimpleMethodLoggingRepository(IRepository<T> decorated)
	{
		this.decorated = decorated;
	}

	public void Add(T entity)
	{
		Log("Simple decorator - Before Adding {0}", entity);
		decorated.Add(entity);
		Log("Simple decorator - After Adding {0}", entity);
	}

	public void Delete(T entity)
	{
		Log("Simple decorator - Before Deleting {0}", entity);
		decorated.Delete(entity);
		Log("Simple decorator - After Deleting {0}", entity);
	}

	public void Update(T entity)
	{
		Log("Simple decorator - Before Updating {0}", entity);
		decorated.Update(entity);
		Log("Simple decorator - After Updating {0}", entity);
	}

	private void Log(string msg, object arg = null)
	{
		Console.ForegroundColor = ConsoleColor.Red;
		Console.WriteLine(msg, arg);
		Console.ResetColor();
	}
}

private static void SimpleMethodLogging()
{
	Console.WriteLine("Start - Simple method logging...");
	IRepository<Order> orderRepository = new SimpleMethodLoggingRepository<Order>(new Repository<Order>());
	Order order = new Order() { Id = 1, CustomerId = 1, Number = "20150317" };
	orderRepository.Add(order);
	orderRepository.Update(order);
	orderRepository.Delete(order);
	Console.WriteLine("End - Simple method logging...");
}

And we get a good result as image below

Decorator Pattern

Whenever a CRUD function is called, our logging engine will come to play. The Decorator Design Pattern does his good job, everything is logged, but we realize that we duplicate code for each CRUD function. Whenever we want to add logging function, we have to duplicate the code rows again. Maybe there is another solution without writing duplicated code? In next section, we’ll learn about RealProxy to avoid these code duplication problem.

2.2 RealProxy – Method logging

RealProxy provides base functionality for proxies. First, we need to create a proxy class inherit from RealProxy. This class will override the Invoke function and fire appropriate events for method’s execution sequences : Before, After or Error.

internal class MethodProxy<T> : RealProxy
{
	private readonly T decorated;

	public MethodProxy(T decorated)
		: base(typeof(T))
	{
		this.decorated = decorated;
	}

	public event EventHandler<IMethodCallMessage> AfterExecute;

	public event EventHandler<IMethodCallMessage> BeforeExecute;

	public event EventHandler<IMethodCallMessage> ErrorExecute;

	public override IMessage Invoke(IMessage msg)
	{
		var methodCall = msg as IMethodCallMessage;
		var methodInfo = methodCall.MethodBase as MethodInfo;
		OnBeforeExecute(methodCall);
		try
		{
			var result = methodInfo.Invoke(decorated, methodCall.InArgs);
			OnAfterExecute(methodCall);
			return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
		}
		catch (Exception ex)
		{
			OnErrorExecute(methodCall);
			return new ReturnMessage(ex, methodCall);
		}
	}

	private void OnAfterExecute(IMethodCallMessage methodCall)
	{
		if (AfterExecute != null)
		{
			var methodInfo = methodCall.MethodBase as MethodInfo;
			AfterExecute(this, methodCall);
		}
	}

	private void OnBeforeExecute(IMethodCallMessage methodCall)
	{
		if (BeforeExecute != null)
		{
			var methodInfo = methodCall.MethodBase as MethodInfo;

			BeforeExecute(this, methodCall);
		}
	}

	private void OnErrorExecute(IMethodCallMessage methodCall)
	{
		if (ErrorExecute != null)
		{
			var methodInfo = methodCall.MethodBase as MethodInfo;
			ErrorExecute(this, methodCall);
		}
	}
}

internal class RealProxyMethodLoggingRepository
{
	public static IRepository<T> Create<T>()
	{
		var repository = new Repository<T>();
		var methodProxy = new MethodProxy<IRepository<T>>(repository);
		methodProxy.BeforeExecute += methodProxy_BeforeExecute;
		methodProxy.AfterExecute += methodProxy_AfterExecute;
		methodProxy.ErrorExecute += methodProxy_ErrorExecute;
		return methodProxy.GetTransparentProxy() as IRepository<T>;
	}

	private static void Log(string msg, object arg = null)
	{
		Console.ForegroundColor = ConsoleColor.Red;
		Console.WriteLine(msg, arg);
		Console.ResetColor();
	}

	private static void methodProxy_AfterExecute(object sender, IMethodCallMessage e)
	{
		Log("RealProxy - After executing '{0}'", e.MethodName);
	}

	private static void methodProxy_BeforeExecute(object sender, System.Runtime.Remoting.Messaging.IMethodCallMessage e)
	{
		Log("RealProxy - Before executing '{0}'", e.MethodName);
	}

	private static void methodProxy_ErrorExecute(object sender, IMethodCallMessage e)
	{
		Log("RealProxy - Error executing '{0}'", e.MethodName);
	}
}

private static void MethodLoggingWithProxies()
{
	Console.WriteLine("Start - RealProxy method logging...");
	IRepository<Order> orderRepository = RealProxyMethodLoggingRepository.Create<Order>();
	Order order = new Order() { Id = 1, CustomerId = 1, Number = "20150317" };
	orderRepository.Add(order);
	orderRepository.Update(order);
	orderRepository.Delete(order);
	Console.WriteLine("End - RealProxy method logging...");
}

The result we get here is as same as from Decorator Design Pattern. We have to write more code, but we can avoid code duplication and choose what we’re really interested in. For example, if we only want to interfere before any CRUD function, then just handle BeforeExecute and skip the others.
Moreover with RealProxy we separate the MethodProxy object and RealProxyMethodLoggingRepository implementation. When this default logging engine doesn’t full fill requirements in some cases we can define another implementation without rewriting the proxy class.

RealProxy Method Pattern

2.3 RealProxy – Object logging

A filter function can be also applied from outside of RealProxy class. For example, we would like to use our logging function only with some concrete object, for example only for Order, we could make a filter Func pointer as code listing below.

class ObjectProxy<T> : RealProxy
{
	private readonly T decorated;

	public ObjectProxy(T decorated)
		: base(typeof(T))
	{
		this.decorated = decorated;
		filter = x => true;
	}

	private Func<object[], bool> filter;
	public Func<object[], bool> Filter
	{
		get { return filter; }
		set
		{
			if (value == null)
				filter = x => true;
			else
				filter = value;
		}
	}

	public event EventHandler<IMethodCallMessage> AfterExecute;

	public event EventHandler<IMethodCallMessage> BeforeExecute;

	public event EventHandler<IMethodCallMessage> ErrorExecute;

	public override IMessage Invoke(IMessage msg)
	{
		var methodCall = msg as IMethodCallMessage;
		var methodInfo = methodCall.MethodBase as MethodInfo;
		if (filter(methodCall.InArgs))
			OnBeforeExecute(methodCall);
		try
		{
			var result = methodInfo.Invoke(decorated, methodCall.InArgs);
			if (filter(methodCall.InArgs))
				OnAfterExecute(methodCall);
			return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
		}
		catch (Exception ex)
		{
			if (filter(methodCall.InArgs))
				OnErrorExecute(methodCall);
			return new ReturnMessage(ex, methodCall);
		}
	}

	private void OnAfterExecute(IMethodCallMessage methodCall)
	{
		if (AfterExecute != null)
		{
			var methodInfo = methodCall.MethodBase as MethodInfo;
			AfterExecute(this, methodCall);
		}
	}

	private void OnBeforeExecute(IMethodCallMessage methodCall)
	{
		if (BeforeExecute != null)
		{
			var methodInfo = methodCall.MethodBase as MethodInfo;

			BeforeExecute(this, methodCall);
		}
	}

	private void OnErrorExecute(IMethodCallMessage methodCall)
	{
		if (ErrorExecute != null)
		{
			var methodInfo = methodCall.MethodBase as MethodInfo;
			ErrorExecute(this, methodCall);
		}
	}
}

internal class RealProxyMethodLoggingRepository
{
	public static IRepository<T> Create<T>()
	{
		var repository = new Repository<T>();
		var methodProxy = new MethodProxy<IRepository<T>>(repository);
		methodProxy.BeforeExecute += methodProxy_BeforeExecute;
		methodProxy.AfterExecute += methodProxy_AfterExecute;
		methodProxy.ErrorExecute += methodProxy_ErrorExecute;
		return methodProxy.GetTransparentProxy() as IRepository<T>;
	}

	private static void Log(string msg, object arg = null)
	{
		Console.ForegroundColor = ConsoleColor.Red;
		Console.WriteLine(msg, arg);
		Console.ResetColor();
	}

	private static void methodProxy_AfterExecute(object sender, IMethodCallMessage e)
	{
		Log("RealProxy - After executing '{0}'", e.MethodName);
	}

	private static void methodProxy_BeforeExecute(object sender, System.Runtime.Remoting.Messaging.IMethodCallMessage e)
	{
		Log("RealProxy - Before executing '{0}'", e.MethodName);
	}

	private static void methodProxy_ErrorExecute(object sender, IMethodCallMessage e)
	{
		Log("RealProxy - Error executing '{0}'", e.MethodName);
	}
}

NotOrder object will won’t work with logging engine.

private static void ObjectLoggingWithProxiesFilteredOut()
{
	Console.WriteLine("Start - RealProxy object logging...");
	IRepository<NotOrder> orderRepository = RealProxyObjectLoggingFactory.Create<NotOrder>();
	NotOrder order = new NotOrder() { Id = 1, CustomerId = 1, Number = "20150317" };
	orderRepository.Add(order);
	orderRepository.Update(order);
	orderRepository.Delete(order);
	Console.WriteLine("End - RealProxy object logging...");
}

RealProxy - Object Logging without Filter

but Order object will work

private static void ObjectLoggingWithProxiesFilteredIn()
{
	Console.WriteLine("Start - RealProxy object logging...");
	IRepository<Order> orderRepository = RealProxyObjectLoggingFactory.Create<Order>();
	Order order = new Order() { Id = 1, CustomerId = 1, Number = "20150317" };
	orderRepository.Add(order);

	order.Number += "-Changed";
	orderRepository.Update(order);
	orderRepository.Delete(order);
	Console.WriteLine("End - RealProxy object logging...");
}

RealProxy - Object Logging with Filter

You can extend the demo by using CustomAttribute to say when you want to turn the log on and off. The code will be then like code listing below

public override IMessage Invoke(IMessage msg)
{
	var methodCall = msg as IMethodCallMessage;
	var methodInfo = methodCall.MethodBase as MethodInfo;
	if (!methodInfo.CustomAttributes
	.Any(a => a.AttributeType == typeof (YourLogAttribute)))
	{
		var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
		return new ReturnMessage(result, null, 0,
		methodCall.LogicalCallContext, methodCall);
	}
	...
}

3. Conclusions

Using RealProxy for AOP implementation keeps our code clean and easy to maintain. The possibility for changing object’s behaviors is enormous. We can not only interfere the method calls but also apply filter to proxy to filter out all things that we want to exclude. Of course, Decorator Design Pattern does his work too, with help of property and Func pointer you’ll get same result of using RealProxy. However I think it’s worth to try some thing new.

Source code: https://bitbucket.org/hintdesk/dotnet-introduction-to-aspect-oriented-programming-with

Leave a Reply

Your email address will not be published. Required fields are marked *