PostSharp-ening my skills with Aspect-Oriented Programming

A senior developer at my company recommended I look into a Visual Studio add-in called PostSharp, so I have been spending my morning working through the tutorials of which a list has been integrated into Visual Studio 2010 as a side-bar.  I was disappointed that PostSharp is not supported by VS 2012 yet.  I’m going to blog a brief summary of the benefits of PostSharp so if anyone wants to start using it, they can take a quick look at the features.  Let’s have a look!

OnExceptionAspect

A simple winform is used for the example and shows a UI for performing CRUD on a list of contacts.  Initially, a specific exception is thrown when you attempt an operation on the in-memory contacts.  The contention here is to obscure the specific error message to prevent the user from knowing more than they need to know (both for simplicity and security) and showing a more generic exception message.  Try/catch code is added to the method to catch the exception, log it and throw an exception with a generic message.

public IQueryable GetByName(string value)
{
    try
    {
        var res = _contactStore.Where(c => c.FirstName.Contains(value) 
                    || c.LastName.Contains(value));

        if (res.Count() < 1)
        {
            ThrowNoResultsException();
        }

        Thread.Sleep(3000);
        return res.AsQueryable();
    }
    catch (Exception ex)
    {
        //Log exception here
        throw new Exception("There was a problem.");
    }
}

Instead of adding specific try/catch blocks to write log messages and throw an exception, the exception and tracing code is moved to a separate class and only decoration code on the existing method is required to implement the tracing and exception handling.

[Serializable]
public class DatabaseExceptionWrapper : OnExceptionAspect
{
    public override void OnException(MethodExecutionArgs args)
    {
        string msg = string.Format("{0} had an error @ {1}: {2}\n{3}", 
            args.Method.Name, DateTime.Now, 
            args.Exception.Message, args.Exception.StackTrace);

        Trace.WriteLine(msg);

        throw new Exception("There was a problem");
    } 
}

Now, all that is needed on our method is the decoration tag pointing to the above method and all our logging and exception handling will be taken care of!

[DatabaseExceptionWrapper]
public IQueryable GetByName(string value)
{
    var res = _contactStore.Where(c => c.FirstName.Contains(value) 
                || c.LastName.Contains(value));

    if (res.Count() < 1)
    {
        ThrowNoResultsException();
    }

    Thread.Sleep(3000);
    return res.AsQueryable();
}

OnMethodBoundaryAspect

As a continuation to OnExceptionAspect, OnMethodBoundaryAspect can be used to profile the starting and ending of method execution.  We create another method that can be applied to any method we want this behaviori on:

[Serializable]
public class MethodTraceAspect : OnMethodBoundaryAspect
{
    public override void OnEntry(MethodExecutionArgs args)
    {
        Debug.WriteLine(args.Method.Name + " started");
    }

    public override void OnExit(MethodExecutionArgs args)
    {
        Debug.WriteLine(args.Method.Name + " finished");
    }
}

We could have also overrided the following methods to test success or failure of the method:

  • OnSuccess – Called only when a method is done executing and there were no exceptions
  • OnException – Called only when a method has stopped executing due to an unhandled exception

Now we can decorate our GetByName method from above with [MethodTraceAspect] and we can see in the output window when our method starts and ends. This is useful, but what if we wanted to apply it to all methods in a class, but don’t want to do the busywork? We can simply apply the same decoration tag to the class and this code will be run for every method in the class. Beautiful!  At an even higher level, we can place the following decoration tag above the namespace declaration and achieve the same behavior for all methods in all classes (exceptions can be made as we will see below):

[assembly: PostSharpDemo1.MethodTraceAspect()]
namespace PostSharpDemo1
{
    ...
}

This is so thorough that at this point, if we run the program the aspect will be applied to the aspect method itself and cause a StackOverflowException. To exclude this aspect from being run on a specific method, we set the AttributeExclude property to true:

[MethodTraceAspect(AttributeExclude = true)]

AttributeExclude will also work at the class and assembly level. The following code will exclude property getters and setters:

[assembly: PostSharpDemo1.MethodTraceAspect(AttributeExclude = true, AttributePriority = 0, 
            AttributeTargetMemberAttributes = MulticastAttributes.CompilerGenerated)]

The AttributePriority property can be used to set which aspects should be run first: (lower number = higher priority)

[MethodTraceAspect(AttributePriority = 10)]
[DatabaseExceptionWrapper(AttributePriority = 20)]
public IQueryable GetByName(string value)
{
     …
}

There are a variety of ways to select aspects to apply to methods

[assembly: PostSharpDemo1.MethodTraceAspect(
                       AttributeTargetTypes = "PostSharpDemo1.Data.*")]

More on OnMethodBoundaryAspect

There are a variety of properties that can be accessed while a method is within the context of an aspect that allow you access parameters, return values, exception values, the method instance itself as well as properties to pass values between aspects. In the following example, an aspect is created that will start a stopwatch and stop it when the method execution ends, recording the time it took the method to run. This code is reusable for any method with a simple decoration. Anyone who has debugged large applications will see the huge benefits in a tool like this for testing execution times of methods (outside of unit testing at least).

[Serializable]
[ProfilerAspect(AttributeExclude = true)]
public class ProfilerAspect : PostSharp.Aspects.OnMethodBoundaryAspect
{
    public override void OnEntry(MethodExecutionArgs args)
    {
        args.MethodExecutionTag = Stopwatch.StartNew();
    }
    public override void OnExit(MethodExecutionArgs args)
    {
        Stopwatch sw = (Stopwatch)args.MethodExecutionTag;
        sw.Stop();
        string output = string.Format("{0} Executed in {1} seconds", 
                            args.Method.Name, sw.ElapsedMilliseconds / 1000);
        System.Diagnostics.Debug.WriteLine(output);
    }
}

Visual Studio Add-In

The extension adds a sidebar to Visual Studio that allows you to see all the aspects you have created and an easy interface for viewing and editing the methods that are affected by each of the aspects.  In your code, methods that are affected by aspects will be underlined and on hover-over, you can view the aspects that are applied to it.  Cool.

Interception Aspects

There are aspects that can be created to run before a method call and encapsulate the method itself.  For instance, you can create methods that you can decorate other methods with to tell them to be run in separate threads.

[Serializable]
public class WorkerThread : MethodInterceptionAspect
{
    public override void OnInvoke(MethodInterceptionArgs args)
    {
        BackgroundWorker bw = new BackgroundWorker();
        bw.DoWork += new DoWorkEventHandler(
            (object sender, DoWorkEventArgs e) =>
            {
                args.Proceed();
            }
        );

        bw.RunWorkerAsync();
    }

}

[Serializable]
public class UIThread : MethodInterceptionAspect
{
    private delegate void InvokeDelegate();

    public override void OnInvoke(MethodInterceptionArgs args)
    {
        Form main = (Form)args.Instance;

        if (main.InvokeRequired)
        {
            main.BeginInvoke(new Action(args.Proceed));

        }else {
            args.Proceed();
        }
    }
}

[WorkerThread]
private void UpdateContactList()
{
…
}

[UIThread]
private void PopulateContactsList(IQueryable contacts)
{ 
…
}

Now, when the UpdateContactList method is run, it will not lock up the UI and allow the PopulateContactsList method to run and update the UI.

Similarly, LocationInterceptAspect can be used to intercept method execution and run before and after execution.  We can set up a class as follows:

public class DemoAspect : LocationInterceptionAspect
{
    public override void OnGetValue(LocationInterceptionArgs args)
    {
        Debug.WriteLine("Get interception by aspect on " + args.LocationName);
        args.ProceedGetValue();
    }

    public override void OnSetValue(LocationInterceptionArgs args)
    {
        Debug.WriteLine("Set interception by aspect on " + args.LocationName);
        args.ProceedSetValue();
    }
}

Now, we can decorate a property with this tag and the above code will run before the get and set methods are called on this property.

[DemoAspect]
public int MyProperty 
{
    get { Debug.WriteLine("Get MyProperty"); return _myProperty; }
    set { Debug.WriteLine("Set MyProperty"); _myProperty = value; } 
}

You can also intercept event calls and the adding and removing of handlers with aspects:

[Serializable]
public class EventAspect : EventInterceptionAspect
{
    public override void  OnAddHandler(EventInterceptionArgs args)
    {
        args.ProceedAddHandler();
        Console.WriteLine("Handler added");
    }

    public override void  OnRemoveHandler(EventInterceptionArgs args)
    {
        args.ProceedRemoveHandler();
        Console.WriteLine("Handler removed");
    }

    public override void OnInvokeHandler(EventInterceptionArgs args)
    {
        args.ProceedInvokeHandler();
        Console.WriteLine("Handler invoked");
    }
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s