This is the fifth, and for now, last, of my series of posts on AspectMap. If you’re new to AspectMap, I’d recommend you start at the beginning and work your way through the tutorials:

Aspect Nesting

This is a fairly easy concept to grasp. When you add an aspect to a method you are wrapping its logic with that of the attribute handler. If you add a second aspect to the same method you are wrapping both the logic of the method and the first attribute handler within the second – nesting them.

We’ll illustrate this with a simple example and a couple of unit tests. Firstly create a new test class:

[TestClass]
public class NestingTests
{
  private static string _output;
}

Notice the _output variable, this will be used to pass information between the aspects and the tests. All the following classes should be created inside the test class. Next up are our attributes and handlers:

public class NestingLevel1Attribute : Attribute {}
public class NestingLevel2Attribute : Attribute {}

public class Level1Handler : AttributeHandler
{
  public override Action<IInvocation> Surround(Action<IInvocation> invocation,
      Attribute sourceAttribute)
  {
    return i =>
    {
      _output += "Level1 Entered\n";
      Console.WriteLine("Level1 Entered");
      invocation(i);
      _output += "Level1 Exited\n";
      Console.WriteLine("Level1 Exited");
    };
  }
 }

public class Level2Handler : AttributeHandler
{
  public override Action<IInvocation> Surround(Action<IInvocation> invocation,
      Attribute sourceAttribute)
  {
    return i =>
    {
      _output += "Level2 Entered\n";
      Console.WriteLine("Level2 Entered");
      invocation(i);
      _output += "Level2 Exited\n";
      Console.WriteLine("Level2 Exited");
    };
  }
}

Nothing complicated, we simply add text to both the _output variable and write it to the console (for ease of testing). Next up a really simple test class:

public interface ITestClass
{
  void DoSomething();
}

public class ItemWithMultipleAttributes : ITestClass
{
  [NestingLevel1]
  [NestingLevel2]
  public void DoSomething()
  {
    _output += "DoSomething\n";
    Console.WriteLine("DoSomething");
  }
}

The DoSomething method just writes to the _output variable, and applies both of our aspects. The final stage of setup is a registry to wire it all together:

public class NestingRegistry : AspectsRegistry
{
  public NestingRegistry()
  {
    ForAspect<NestingLevel1Attribute>().HandleWith<Level1Handler>();
    ForAspect<NestingLevel2Attribute>().HandleWith<Level2Handler>();
    For<ITestClass>().Use<ItemWithMultipleAttributes>().
        EnrichWith(AddAspectsTo<ITestClass>);
  }
}

It’s worth pausing at this point to go through how AspectMap will order your aspects by default. From now on I will use the terms closer and further away for this – closer means that the aspect wraps closer to the source method, further away is at a further remove, wrapping the method and other aspects. All control over this prioritisation occurs in your registry, not in the classes, although I plan to add priorities at this level in a future release.

When writing your ForAspect declarations, those that appear first will be applied closer to the source method. We can confirm this with a test:

[TestMethod]
public void NestingUsesCorrectOrder()
{
  ObjectFactory.Initialize(x => x.AddRegistry(new NestingRegistry()));

  ITestClass testClass = ObjectFactory.GetInstance<ITestClass>();
  _output = "";

  testClass.DoSomething();

  Assert.AreEqual("Level2 Entered\nLevel1 Entered\nDoSomething\nLevel1 Exited\nLevel2 Exited\n", _output);

  ObjectFactory.ResetDefaults();
}

If you run this and look in your results window you should see the following from the Console.WriteLine statements:

Level2 Entered
Level1 Entered
DoSomething
Level1 Exited
Level2 Exited

Prioritising

Great. But what about situations where you’d like to define which aspect has priority? You can control this with the WithPriority method in your registry:

ForAspect<NestingLevel1Attribute>().WithPriority(1).HandleWith<Level1Handler>();
ForAspect<NestingLevel2Attribute>().WithPriority(2).HandleWith<Level2Handler>();

Aspects with a higher priority will be applied closer to the source method. You can confirm this by making the change above and re-running your test. It will fail, but the console will now read:

Level1 Entered

Level2 Entered
DoSomething
Level2 Exited
Level1 Exited

Awesome. You should now have all the skills you need to use AspectMap in any of your applications!

This is it from me on AspectMap for a while – I’ll be concentrating on adding a few new features for a 1.5 release in the fairly near future, and the source will be available on codeplex this week. I’m totally open to input as to what is in this next release so feel free to get in touch on here or through twitter, or even better, get involved when I open source it.  Thanks for reading!