Aspect Orientated Programming in Castle Windsor
A while back I did a lunch & learn on using the Castle Windsor inversion of control library to build a very basic interceptor, demonstrating the basics of AOP in C#.
I'm surprised at how little AOP I've seen in the wild and thought it would be worthwhile sharing the details from the talk here.
All the code from below can be found in this GitHub repo.
What is aspect orientated programming?
As applications grow it's common to start seeing duplication of what's known as "cross cutting concerns" across the solution. These are pieces of logic that apply to whole layers of the codebase rather than individual classes, for example:
- Logging
- Performance / statistics gathering
- Security / authorisation
Code starts to crop up with the first few lines of manager methods looking awfully similiar like so:
public CardDetails GetCardDetails(Guid cardId)
{
if (!_authenticator.CurrentUserPermittedToView(cardId))
{
_logger.LogWarning(
"User '{0}' not permitted to view card id '{1}'",
currentUser.UserId,
cardId);
}
return _repository.GetCardDetails(cardId);
}
If you follow the traditional Manager - Repository pattern you may see each manager always takes in the _authenticator
and _logger
dependencies and performs calls similar to the above on all public methods.
This means:
- The methods are harder to read, there is more "noise" around the business logic
- Developers have to be careful to ensure they perform the correct authentication and logging for each new method at manager level
- We introduce tighter coupling to a small set of components dealing with cross-cutting concerns
Aspect oriented programming is an approach that effectively "injects" pieces of code before or after an existing operation. This works by defining an Inteceptor
wrapping the logic being invoked then registering it to run whenever a particular set/sub-set of methods are called.
Writing our first interceptor
Below is a very basic permissions interceptor that seeks to check whether the current user is allowed to view the results of particular method:
// IInterceptor is part of Castle Windsor, this is the interface we must fulfil to
// write our interceptor. The important method here is Intercept(IInvocation invocation).
public class PermissionsInterceptor : IInterceptor
{
// IInvocation is a wrapper around the method being intercepted and includes
// valuable meta-data we can use to identify whether any actual should be performed
// prior to proceeding with the call.
// Note we can even obtain the properties being passed through to the method here
// if we wanted to incorporate them into the cross cutting logic.
public void Intercept(IInvocation invocation)
{
Console.WriteLine("Checking Permissions");
// Here we want to only run permissions checks against public methods
// This means we don't subsequentially duplicate the calls against private methods
// internal to the class.
if (!invocation.Method.IsPublic)
{
// Run the intercepted method as normal.
invocation.Proceed();
}
// I'll go through this later, attributes can be used to further augment the
// flow of intercepted logic.
else if (AttributeExistsOnMethod<DoNotPerformPermissionCheck>(invocation))
{
// Run the intercepted method as normal.
invocation.Proceed();
}
// Mock call checking if the user has the relevant privileges.
else if (PermissionsStub.IsUserPermittedToContinue)
{
// Run the intercepted method as normal.
invocation.Proceed();
}
else
{
// As we haven't run Proceed, the wrapped invocation will not be run.
throw new SecurityException("The user is not permitted to run this method");
}
}
private static bool AttributeExistsOnMethod<AttributeToCheck>(IInvocation invocation)
{
var attribute = Attribute.GetCustomAttribute(
invocation.Method,
typeof(AttributeToCheck),
true);
return attribute != null;
}
}
With the interceptor written we can tell our DI container to run it for a particular sub-set of methods.
In the case of Castle Windsor, any class we want to apply interceptors to must be registered with the DI container. This will allow Windsor to run the invocation through our interceptor on our behalf.
// Register the interceptor itself as if it was any other standard component.
_container.Register(
Component.For<IInterceptor>()
.ImplementedBy<PermissionsInterceptor>());
// Get all public classes in this assembly with their first interface.
// A quick, albeit dirty, way of registering all dependencies we're interested in.
var toRegister = Classes.FromThisAssembly()
.Where(type => type.IsPublic)
.WithService
.FirstInterface();
// We register these classes as normal, however as we do so we tell Windsor to run
// the registered interceptors first.
_container.Register(
toRegister
.Configure(delegate(ComponentRegistration c)
{
var x = c.Interceptors(InterceptorReference.ForType<IInterceptor>()).Anywhere;
}));
Now let's look at one of the manager interfaces protected by this interceptor:
public interface ITradeManager
{
/// <summary>
/// Adds a new trade to the list.
/// </summary>
/// <param name="tradeToAdd">The trade to add to the listing.</param>
void AddTrade(Trade tradeToAdd);
/// <summary>
/// Returns a list of all the trades executed today.
/// </summary>
/// <returns>A list of trades.</returns>
List<Trade> GetTodaysTrades();
/// <summary>
/// Logs the current user in to the system.
/// </summary>
[DoNotPerformPermissionCheck]
void Login();
}
As we have used a very open registration with Windsor, the interceptor will run against all methods in the manager. However, using attributes we can modify the behaviour if we wish, so in our case we have the DoNotPerformPermissionCheck
attribute decorating the Login() method. This ensures we don't run the permissions check when the user is attempting to authenticate.
(Note: you can use attributes to pass additional details to the interceptor; such as which specific permission / role to check for and messages to log on failure).
With this complete we have all the elements we need, so when running our code, the permissions check will be performed without duplicating it across our managers:
[Test]
public void GetTodaysTrades_UserIsPermittedToViewTrades_TradesAreReturned()
{
PermissionsStub.IsUserPermittedToContinue = true;
var trades = _tradeManager.GetTodaysTrades();
Assert.IsNotNull(trades);
Assert.IsTrue(trades.Any());
}
[Test]
public void GetTodaysTrades_UserIsNotPermittedToViewTrades_SecurityExceptionThrown()
{
PermissionsStub.IsUserPermittedToContinue = false;
Assert.Throws<SecurityException>(() => _tradeManager.GetTodaysTrades());
}
Advantages and disadvantages of AOP
As show above, the paradigm is quite powerful when dealing with cross cutting concerns. Specifically it ensures:
- Noise around the business logic is reduced
- Reduces bugs caused by missing cross cutting logic (forgot to add logging etc)
- Reduces duplication of common code
- Decouples cross cutting concerns
It isn't however a silver bullet. The writers of Simple Injector have chosen to leave interceptors out of their DI container by default for example. Commonly the issues with AOP are:
- "Hidden" logic; similar to infamous triggers in SQL Server, a developer may see unintended behaviour due to not knowing interceptors are running
- Promiscuous registrations of interceptors leading to the logic being run more often than necessary (in the above example we've registered it for every...single...method.)
- Performance concerns; in Castle Windsor AOP is through dynamic (read: reflection based) injection.
As an alternative you can use the decorator pattern (not to push for Simple Injector or anything, but they've got good support for it!) or opt for a more functional approach to wrap underlying logic in a more transparent way.
Any thoughts / questions / musings: feel free to leave them in the comments below.