Tech tutorials Aspect-Oriented Programming With Castle Windsor
By Insight Editor / 15 Sep 2015 , Updated on 16 May 2019 / Topics: Application development
By Insight Editor / 15 Sep 2015 , Updated on 16 May 2019 / Topics: Application development
Most application development will contain common functionality that spans multiple layers, such as authentication, communication, exception management and logging. Such functionality is generally thought of as a cross-cutting concern because it affects the entire application and should be centralized in one location. Unfortunately, though, how many times have you seen code modifications like this?
The business requirements that are being satisfied here are:
This code usually starts in a handful of places but inevitably is copied and pasted throughout the application as it’s easier than architecting a solution that would be more manageable.
This approach works. However, this creates some rather troubling issues:
How can we align the implementation with better practices? Aspect-Oriented Programming (AOP) to the rescue.
The goal of AOP is to increase modularity by allowing the separation of cross-cutting concerns. It does so by adding behavior to existing code without modifying the code itself and, in C#, is usually done with a class or method attribute. This allows behaviors that aren’t critical to the business logic to be added without repeating code or creating tightly coupled code.
To maintain the business requirements, we can use our existing IoC container, Castle Windsor. If you’re using a different container, you should be able to extract this approach to suit your needs, assuming it supports interception. If you’re not familiar with Castle Windsor, you’ll want to learn more about Castle Windsor interception.
class Program
{
private static WindsorContainer _kernel;
static void Main(string[] args)
{
_kernel = new WindsorContainer();
_kernel.Install(FromAssembly.This());
var pData = _kernel.Resolve<IProjectData>();
try
{
Run(pData);
}
catch (Exception exception)
{
System.Console.WriteLine("Program threw an exception: {0}", exception.Message);
}
System.Console.Read();
}
static void Run(IProjectData data)
{
var d = data.GetAllItems();
System.Console.WriteLine("Get some Project Data: {0} items", d.Count);
foreach (var item in d)
{
System.Console.WriteLine("\t({0}) Name:{1}, Type:{2}", item.Id, item.Name, item.ItemType);
}
}
}
Let’s break down what’s going on here.
How did we get that data? Looking at the installer, we can see we’re resolving the dependency IProjectData with the implementation of ProjectDataLocal. Visit GitHub to learn more about Castle Windsor installers for registering dependencies.
public class DataInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Component.For<IProjectData>()
.ImplementedBy<ProjectDataLocal>());
}
}
To keep things simple, ProjectDataLocal is a hard-coded list of items we return, but could just as easily call into a database.
public class ProjectDataLocal : IProjectData
{
private List<ProjectItem> _masterList;
public ProjectDataLocal()
{
_masterList = new List<ProjectItem>
{
new ProjectItem { Id = Guid.Parse("5843b73d-45f7-4284-86cf-c2f07821e01d"), Name ="Item 1", ItemType = "A" },
new ProjectItem { Id = Guid.Parse("9815b73d-45f7-4284-86cf-c2f07821e01d"), Name ="Item 2", ItemType = "A" },
new ProjectItem { Id = Guid.Parse("2100b73d-45f7-4284-86cf-c2f07821e01d"), Name ="Item 3", ItemType = "B" },
new ProjectItem { Id = Guid.Parse("8688b73d-45f7-4284-86cf-c2f07821e01d"), Name ="Item 4", ItemType = "C" }
};
}
public List<ProjectItem> GetAllItems()
{
return _masterList;
}
public ProjectItem GetItemById(Guid id)
{
return _masterList.Single(x => x.Id == id);
}
public ProjectItem GetItemByName(string name)
{
return _masterList.Single(x => x.Name == name);
}
}
So far, so good. But now we need to update the code to handle the business requirements. Of course, we could fall back into bad habits and do something like this:
public List<ProjectItem> GetAllItems()
{
var start = DateTime.Now;
try
{
return _masterList;
}
catch (Exception exception)
{
LogException(exception);
}
finally
{
LogTiming(start, DateTime.Now);
}
}
But we can do better … and since we have Castle Windsor already in place, it becomes rather simple.
Let’s first create an Aspect class that will handle two ideas:
/// <summary>
/// Abstract class to wrap Castle Windsor's IInterceptor to only fire if the method or class is decorated with this attribute.
/// </summary>
public abstract class Aspect : Attribute, IInterceptor
{
public void Intercept(IInvocation invocation)
{
if (!CanIntercept(invocation, GetType()))
{//method is NOT decorated with the proper aspect, continue as normal
invocation.Proceed();
return;
}
ProcessInvocation(invocation);
}
/// <summary>
/// Determine if the intercepted class or method is decorated with the current attribute
/// Classes decorated will process if decorated on ALL methods
/// Methods decorated will process if decorate
/// </summary>
/// <param name="invocation"></param>
/// <param name="type"></param>
/// <returns></returns>
private bool CanIntercept(IInvocation invocation, Type type)
{
return invocation.TargetType.CustomAttributes.Any(x => x.AttributeType == type) ||
invocation.MethodInvocationTarget.CustomAttributes.Any(x => x.AttributeType == type);
}
/// <summary>
///
/// </summary>
/// <param name="invocation"></param>
public abstract void ProcessInvocation(IInvocation invocation);
}
This is an abstract class, which lets us do two things. First, it requires an implementation to be instantiated so we can create and call abstract methods that must be present in the concrete class (ProcessInvocation, for example). Second, it will check the class and method for itself, since it’s also an attribute, to determine if it should process the invocation.
For our application, we’ll need two implementations to handle both business requirements. We could, of course, combine these into one, but then we’d be stepping backward and violating single responsibility.
To create an implementation, all we need to do is inherit from Aspect and implement its one abstract method, ProcessInvocation().
Each aspect is writing to the console just to keep the example simple. In a real application, it would call the logging implementation.
public class ExceptionAspect : Aspect
{
public override void ProcessInvocation(IInvocation invocation)
{
try
{
invocation.Proceed();
}
catch (Exception exception)
{
Console.WriteLine("Exception Intercepted: {0}", exception.Message);
throw;//re-throw
}
}
}
public class TimingAspect : Aspect
{
public override void ProcessInvocation(IInvocation invocation)
{
var sw = Stopwatch.StartNew();
invocation.Proceed();
sw.Stop();
Console.WriteLine("({0})Elapsed: {1}", invocation.MethodInvocationTarget.Name, sw.Elapsed);
}
}
To wire this, we need to modify our installer file and register the interceptors:
public class DataInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Component.For<ExceptionAspect>());
container.Register(Component.For<TimingAspect>());
container.Register(Component.For<IProjectData>()
.ImplementedBy<ProjectDataLocal>()
.Interceptors(
typeof(ExceptionAspect),
typeof(TimingAspect)));
}
}
The first thing required is to register the interceptors with themselves. Then we can attach them to our IProjectData by type.
When Castle Windsor resolves the dependency, it will inject these into the stack and call the interceptors in succession, and THEN call the method.
The basic flow looks something like this:
Notice we placed the TimingAspect last so that we get more accurate performance information on GetAllItems().
If we run our application at this point, we won’t see any changes since we haven’t set the attributes to our implementation, so let’s do that now:
[ExceptionAspect]
[TimingAspect]
public List<ProjectItem> GetAllItems()
{
return _masterList;
}
And our output where we can see the elapsed time printed on the screen:
Now, if our application was to throw an exception, we’d expect our output to be:
Now that we have our AOP solution in place, we can start to add configuration-based execution.
Let’s say a new business requirement is added, which requires us to remove TimingAspect from executing in production due to its overhead. For this, we head back over to the installer file and make a change.
public class DataInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
var isProd = ConfigurationManager.AppSettings["IsProduction"] == "True";
container.Register(Component.For<ExceptionAspect>());
container.Register(Component.For<TimingAspect>());
var iProjData = Component.For<IProjectData>().ImplementedBy<ProjectDataLocal>();
iProjData.Interceptors(typeof(ExceptionAspect));
if (!isProd)
{
iProjData.Interceptors(typeof(TimingAspect));
}
container.Register(iProjData);
}
}
As you can see, we always intercept ExceptionAspect for the IProjectData dependency, but we only intercept TimingAspect if we have a configuration that has a key set to True. This allows us to move our execution behavior to the configuration where we can selectively turn things on or off without having to recompile the application.
AOP is a clean way to selectively target method wrapping, and using an IoC container makes short work of the implementation. We’ve set up the ability to easily add new aspects we can selectively use in our code to meet business needs. So, the next time you start to copy and paste code similar to what you see here, stop and think about if AOP is the right fit for your application.