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
1public class BusinessLogic
{
1 IDataProvider data;
2```
3
4```
5 public BusinessLogic(IDataProvider data)
{
1 this.data = data;
}
\[Security\]
1 public DataRecord GetItem(int customerId)
{
1 Debug.WriteLine("BusinessLogic.GetItem");
return this.data.GetItemFromDB(customerId);
1 }
}
1
2So 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]
1 public class SecurityAttribute :MethodInterceptionAspect
{
1 public override void OnInvoke(MethodInterceptionArgs args)
{
1 Debug.WriteLine("SecurityAttribute.OnInvoke");
2```
3
4```
5 // this assumes we know the type of arguement and that we can
if (MembershipProviderFactory.GetProvider().CanCurrentUserViewThisItem((int)args.Arguments\[0\]) == true)
1 {
Debug.WriteLine("SecurityAttribute.OnInvoke: We have rights to view");
1 base.OnInvoke(args);
}
1 else
{
1 Debug.WriteLine("SecurityAttribute.OnInvoke: We dont have rights to view");
}
1 }
}
1
2As 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](http://www.typemock.com/index-b.php) to fake it all, so my tests were as shown below. I found this way of working much quicker for my purposes.
///
1 /// test for both the success and failure paths of the attribute
///
1 \[TestClass\]
public class Tests
1 {
\[Isolated\]
1 \[TestMethod\]
public void When\_the\_membership\_provider\_gives\_access\_the\_data\_is\_returned()
1 {
// arrange
// create a fake objects
1 var fakeIMembershipProvider = Isolate.Fake.Instance<IMembershipProvider>();
var fakeISqlProvider = Isolate.Fake.Instance<ISqlProvider>();
// create real objects
1 var fakeData = new DataRecord();
var bl = new BusinessLogic(fakeISqlProvider);
// Set that when we call the factory method we get the fake membership system
1 Isolate.WhenCalled(() => MembershipProviderFactory.GetProvider()).WillReturn(fakeIMembershipProvider);
// Set when we call the DB layer we get the fake object
1 Isolate.WhenCalled(() => fakeISqlProvider.GetItemFromDB(0)).WillReturn(fakeData);
// Set that we are allowed to see the item
1 Isolate.WhenCalled(() => fakeIMembershipProvider.CanCurrentUserViewThisItem(0)).WillReturn(true);
2```
3
4```
5 // act
var actual = bl.GetItem(1);
// assert
1 Assert.AreEqual(fakeData, actual);
Isolate.Verify.WasCalledWithExactArguments(() => fakeISqlProvider.GetItemFromDB(1));
1 }
2```
3
4```
5 \[Isolated\]
\[TestMethod\]
1 public void When\_the\_membership\_provider\_does\_not\_give\_access\_the\_data\_is\_returned()
{
1 // arrange
2```
3
4```
5 // create a fake objects
var fakeIMembershipProvider = Isolate.Fake.Instance<IMembershipProvider>();
1 var fakeISqlProvider = Isolate.Fake.Instance<ISqlProvider>();
2```
3
4```
5 // create real objects
var fakeData = new DataRecord();
1 var bl = new BusinessLogic(fakeISqlProvider);
2```
3
4```
5 // Set that when we call the factory method we get the fake membership system
Isolate.WhenCalled(() => MembershipProviderFactory.GetProvider()).WillReturn(fakeIMembershipProvider);
1 // Set when we call the DB layer we get the fake object
Isolate.WhenCalled(() => fakeISqlProvider.GetItemFromDB(0)).WillReturn(fakeData);
1 // Set that we are not allowed to see the item
Isolate.WhenCalled(() => fakeIMembershipProvider.CanCurrentUserViewThisItem(0)).WillReturn(false);
// act
1 var actual = bl.GetItem(1);
2```
3
4```
5 // assert
Assert.AreEqual(null, actual);
1 Isolate.Verify.WasNotCalled(() => fakeISqlProvider.GetItemFromDB(1));
}
}
1
2This all work beautifully and I have to say this was nice and straight forward to code. The code looks clean and using [Reflector](http://www.red-gate.com/products/reflector/) the generated code is OK tool.
3
4My only worries are
5
61. 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.
72. 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.
8
9So 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.