First look at Postsharp AOP framework for .NET

At the Software Craftsmanship 2010 conference I met Gael Fraiteur of Sharpcrafters, he had given a talk on Aspect Oriented Programming AOP.Since the conference I have had a chance to look at his Postsharp AOP product for .NET.

I decided to do a quick spike project for a tender I have been working on, the requirement is to add a security model to an existing .NET assembly. Usually this would have entailed adding some security logic at the start of each public method to implement the security model. Using AOP I hoped I would be able to get the same effect by adding an attribute to the classes/methods, hopefully making the changes easier to read and quicker to develop.

So I have the following business logic I wish to added security too. All I did was add the [Security] attribute to the business logic method

public class BusinessLogic
{
    IDataProvider data;
 
    public BusinessLogic(IDataProvider data)
    {
        this.data = data;
    }
 
    [Security]
    public DataRecord GetItem(int customerId)
    {
        Debug.WriteLine("BusinessLogic.GetItem");
        return this.data.GetItemFromDB(customerId);
    }
}


So what is in the AOP attribute? Basically I use the AOP framework to intercept the method call, and before the method is invoked I make a call to a factory method to get an instance of the security provider and check if I have the rights to run the method.



[Serializable]
 public class SecurityAttribute :MethodInterceptionAspect
 {
     public override void OnInvoke(MethodInterceptionArgs args)
     {
         Debug.WriteLine("SecurityAttribute.OnInvoke");
 
         // this assumes we know the type of arguement and that we can 
         if (MembershipProviderFactory.GetProvider().CanCurrentUserViewThisItem((int)args.Arguments[0]) == true)
         {
             Debug.WriteLine("SecurityAttribute.OnInvoke: We have rights to view");
             base.OnInvoke(args);
         }
         else
         {
             Debug.WriteLine("SecurityAttribute.OnInvoke: We dont have rights to view");
         }
     }
 }


As it was a spike project I did not bother to write the security provider (or the DB provider for that matter). I used Typemock Isolator to fake it all, so my tests were as shown below. I found this way of working much quicker for my purposes.



/// <summary>
  /// test for both the success and failure paths of the attribute
  /// </summary>
  [TestClass]
  public class Tests
  {
      [Isolated]
      [TestMethod]
      public void When_the_membership_provider_gives_access_the_data_is_returned()
      {
          // arrange
 
          // create a fake objects
          var fakeIMembershipProvider = Isolate.Fake.Instance<IMembershipProvider>();
          var fakeISqlProvider = Isolate.Fake.Instance<ISqlProvider>();
 
          // create real objects
          var fakeData = new DataRecord();
          var bl = new BusinessLogic(fakeISqlProvider);
 
          // Set that when we call the factory method we get the fake membership system
          Isolate.WhenCalled(() => MembershipProviderFactory.GetProvider()).WillReturn(fakeIMembershipProvider);
                      // Set when we call the DB layer we get the fake object
          Isolate.WhenCalled(() => fakeISqlProvider.GetItemFromDB(0)).WillReturn(fakeData);
          // Set that we are allowed to see the item
          Isolate.WhenCalled(() => fakeIMembershipProvider.CanCurrentUserViewThisItem(0)).WillReturn(true);
 
          // act
          var actual = bl.GetItem(1);
 
          // assert
          Assert.AreEqual(fakeData, actual);
          Isolate.Verify.WasCalledWithExactArguments(() => fakeISqlProvider.GetItemFromDB(1));
      }
 
      [Isolated]
      [TestMethod]
      public void When_the_membership_provider_does_not_give_access_the_data_is_returned()
      {
          // arrange
 
          // create a fake objects
          var fakeIMembershipProvider = Isolate.Fake.Instance<IMembershipProvider>();
          var fakeISqlProvider = Isolate.Fake.Instance<ISqlProvider>();
 
          // create real objects
          var fakeData = new DataRecord();
          var bl = new BusinessLogic(fakeISqlProvider);
 
          // Set that when we call the factory method we get the fake membership system
          Isolate.WhenCalled(() => MembershipProviderFactory.GetProvider()).WillReturn(fakeIMembershipProvider);
          // Set when we call the DB layer we get the fake object
          Isolate.WhenCalled(() => fakeISqlProvider.GetItemFromDB(0)).WillReturn(fakeData);
          // Set that we are not allowed to see the item
          Isolate.WhenCalled(() => fakeIMembershipProvider.CanCurrentUserViewThisItem(0)).WillReturn(false);
 
          // act
          var actual = bl.GetItem(1);
 
          // assert
          Assert.AreEqual(null, actual);
          Isolate.Verify.WasNotCalled(() => fakeISqlProvider.GetItemFromDB(1));
      }
 
  }


This all work beautifully and I have to say this was nice and straight forward to code. The code looks clean and using Reflector the generated code is OK tool.



My only worries are



  1. That of performance, but after looking at the code I can’t see that the AOP framework generated code is any great deal less efficient that me adding security methods calls in all the business method. Using Postsharp would certainly require much less repetitive coding. In my spike the security factory strikes me as the bottleneck, but this is my problem, not the frameworks, and can be addressed with a better design pattern to make sure it is not created on every method call.
  2. I can see complexity appearing depending on handling the parameters being passed between the attribute and method being invoked. In my spike I need to know order of the parameters so I could pass the correct one to my security methods, however again I don’t see this as being a major stumbling block, the framework could provide something I am unaware of or I just need to write few forms of the security aspect constructor.




So will I be using Postsharp? I suppose immediately it depends if I win this tender, but I have to say I like what I saw from this first usage.