Click here for Part B
I've been looking at ways to improve the quality of my team's code by removing "unrelated" code from within methods. By that I mean things like opening transactions, caching and exception handling.
For instance, code like that might be quite common:
public object GetSomething()
{
try
{
object o = HttpContext.Current.Cache["MySomething"];
if (o == null) return o;
using (TransactionScope scope = new TransactionScope())
{
o = DataAccess.ActuallyGetSomething();
scope.Complete();
}
HttpContext.Current.Cache["MySomething"] = o;
return o;
}
catch (Exception x)
{
ExcptionPolicy.HandleException(x, "MyPolicy");
}
}
As you can see, it might look like this code does a lot of things, but actually it's only data access wrapped with all these really required wrappers: transactions, caching, exceptions. And if I wanted to add logging in the beginning and end of every method there will be hell to pay...
Well, In reality it's not really that bad. The wrapping code is spread over several layers, and we're also using an anonymous method mechanism to reduce copy-pasting of code, but still. I want to get this ugliness out of my code.
This is what I want:
[EnsureTransaction]
[HandleException]
[Cached("MySomething")]
public object GetSomething()
{
return DataAccess.ActuallyGetSomething();
}
In order to reach this attributed heaven I knew I would probably have to use a technique I heard a lot about but never actually tried to use - Aspect Oriented Programming, or AOP. Among other things, AOP allows you to inject advice (some code or behavior) at join-points - locations in your code, like at beginning or end of a method. And it allows you to do this in a cross-cutting manner, which means you don't have to write any more code inside your method to make it work.
So I went to look for a .NET AOP framework that we might be able to use. I admit I didn't invest that much time in searching and checking out the different options (and believe me, there's plenty). I went for the first framework I found that allows me to write my beloved attributes as quickly as possible and to easily check if the damn thing even works.
The framework I finally found is called PostSharp, which is a post compiler for .NET. What's a post compiler? You might ask. Well, basically, a .NET post compiler lets you compile your code into an assembly, and then it changes your IL code. Freaky, eh? PostSharp achieves this by using some MS-Build tasks that hunts, among other things, these special attributes and replaces them with code. What kind of code? Well, whatever you tell it to.
Finally, I was able to implement the attributes I wanted. There's one:
[Serializable]
[global::System.AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
public sealed class CachedAttribute : OnMethodInvocationAspect
{
private readonly string _cacheKey;
public CachedAttribute(string cacheKey)
{
this._cacheKey = cacheKey;
}
public string CacheKey
{
get { return this._cacheKey; }
}
public override void OnInvocation(MethodInvocationEventArgs eventArgs)
{
object cached = HttpContext.Current.Cache[CacheKey];
if (cached == null)
{
cached = eventArgs.Delegate.DynamicInvoke(eventArgs.GetArguments());
HttpContext.Current.Cache[CacheKey] = cached;
}
eventArgs.ReturnValue = cached;
}
}
What you can see here, is an attribute that inherit's from PostSharp's OnMethodInvocationAspect base class. Any call to a method marked with my cached attribute will be replaced to a call to a different method, which first checks for the specified CacheKey (look at the OnInvocation method - the methodInstance parameter is a delegate to the wrapped method).
And there's another one:
[Serializable]
[global::System.AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
private sealed class HandleExceptionAttribute : OnMethodBoundaryAspect
{
public override void OnEntry(MethodExecutionEventArgs eventArgs)
{
base.OnEntry(eventArgs);
}
public override void OnExit(MethodExecutionEventArgs eventArgs)
{
base.OnExit(eventArgs);
}
public override void OnException(MethodExecutionEventArgs eventArgs)
{
base.OnException(eventArgs);
ExceptionPolicy.HandleException(eventArgs.Exception, "General");
}
}
This is even simpler: Using the OnMethodBoundaryAspect, I can inject code at the beginning and end of the method. Here I'm using this to handle exceptions (using Enterprise Library ExceptionPolicy object), but I can use this for transactions/logging/whatever. Excuse me for not implementing EnsureTransactionAttribute for now, but I will leave that for the reader :)
And voila:
[HandleException]
[Cached("MySomething")]
public object GetSomething()
{
return DataAccess.ActuallyGetSomething();
}
In order to make all this work, you have to:
1. Download and install PostSharp. You should download the latest build from the daily builds page.
2. The install will modify your standard build process (more on that in Part B), so you will have to define the constant POSTSHARP in your project properties in VisualStudio, in order to tell the Post-Compiler to kick in and do the dirty stuff. (Project properties -> Build -> General -> Conditional Compilation Symbols).
3. And of course, you have to reference PostSharp's DLLs (specifically - PostSharp.Laos, PostSharp.Public) which are automatically installed in your GAC.
And that's it! Just use these attributes on your methods, compile (yeah, the post-compiling is gonna make this a bit longer) and see how the magic works.
Hope you enjoyed this. In part B we'll talk more about how this whole thing works, I'll show you some more tricks you can use, talk about some pitfalls you might want to avoid and conclude with the advantages and disadvantages of using a post compiler (That is, of course, if I won't change my mind by the time I write the second part and decide that I want to talk about flowers instead. Or something).
Good night, and have a pleasant tomorrow!
Updated 2.2.2007: changed to use a newer build of PostSharp.
It took me a while to find a decent free blog I can use. My main issue was finding a blog that allowed me to easily post formatted c# code I copy from Visual Studio. Every free blog I checked out seemed to crap the code up.
Finally, I've found the following solution:
- Open a blog using Microsoft's Windows Live Spaces.
- Download the Windows Live Writer. A free desktop software which allows youto edit your posts offline and then publish them to your Windows Live Spaces blog or any other blog (although I haven't tried it with other blogs, but they claim it should work). Note that it's still a beta, and has quite a few bugs...
- Download a plugin for Windows Live Writer that allows you to format code in several languages, including C#, html and many more.
Here's the result:
public DataTable[] Identify(Geometry geom)
{
//... Do some stuff ...
if (!(geom is Point))
throw new NotSupportedException();
//... Do some more stuff ...
}
And now everything is ready for my next posts...
Hey all, thanks for checking out my brand new .NET blog.
My name is Doron Yaacoby, and I've been an ASP.NET/C# developer for the last couple of years. Here I will share with you my ideas, insights and other code (or design) related issues I might encounter. Please feel free to comment about anything I post here, your opinion is of great importance to me.
As you can tell, the blog layout is rather empty at the moment, but I will add more lists, categories, blogs and other stuff in the very near future.
Note that you can sign up for an RSS feed using the link at the bottom left of the page.
So good luck to me, and hope you'll find this blog useful.