Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Better Exception Handling with AOP

4.60/5 (6 votes)
10 Oct 2006CPOL6 min read 1   772  
Exception Handling with AOP can reduce the lines of code for exception handling, thus improving agility of the application and code maintenance

Index

  1. Introduction
  2. The Problems
  3. Consolidating Duplicate Exception Handling in Aspect
  4. Refactoring to Better Exception Handling
  5. Conclusion
  6. References

Introduction

The concept of using AOP for exception handling has been around for a while. The two papers listed in the Reference section list advantages of this approach such as reducing lines of code for exception handling by a factor of 4, better support for different configurations of exceptional behaviors, etc. Most articles on this topic are based on AspectJ for Java. This article tries to give you a concrete example using C# and Spring.Net AOP.

The Problems

Assume we are writing a data access object(DAO) OrderDAO, as shown below. Imaging the Logger is a Log4Net's ILog object. When a DbException occurs, we want to log some debugging information. In this trivial case, we log the ToString of the argument(s).

C#
public class OrderDAO: IOrderDAO
{
    ...
    public void UpdateOrder(Order order){
        try{
            Foo();
        }catch(DbException e){
            Logger.Debug(""+order);
            throw;
        }
    }    
    public void GetOrder(int OrderID)
    {
        try{
            Foo2();
        }catch(DbException e){
            Logger.Debug(""+OrderID);
            throw;
        }
    }
    /// <summary>
    /// <throws>DbException</throws>
    /// </summary>
    private void Foo()
    {            
        // throw instance of DbException
    }
    /// <summary>
    /// <throws>DbException</throws>
    /// </summary>
    private void Foo2()
    {
        // throw instance of DbException
    }   
}

With today's IDEs, we can churn out exception handling code like this in no time. We feel good at the end of the day because we have handled lots of exceptions.

But wait. There are several issues with this code. The code in all catch blocks is similar. It is the incarnation of the concern "Log debugging information and rethrow the exception". Duplication is rarely good. In this traditional approach, the ToString may not return all information about the argument objects. It is hard to log all information if the object is huge. If we add more properties to the Order class, it is likely that we forget to update the catch block(s). Then we will not have the new properties for debugging. Is there a way to consolidate the duplicate exception handling code?

Consolidating Duplicate Exception Handling in Aspect

Let's look at how AOP removes duplication and provides us with more thorough debugging information.

IThrowsAdvice

In Spring.Net AOP module, an IThrowsAdvice's AfterThrowing is invoked after an intercepted method throws an exception. The AfterThrowing method gives us access to the target, which throws the exception, and method arguments. Our advices will implement this interface. The thrown exception keeps propagating to upper call stack after AfterThrowing.

LogArgumentsThrowsAdvice

What this advice will do is iterate through the arguments and call the ILog.Debug(object). It is too simple. The code speaks better for itself.

C#
class LogArgumentsThrowsAdvice : IThrowsAdvice
{
    private ILog Logger;
    public LogArgumentsThrowsAdvice()
    {
        log4net.Config.XmlConfigurator.Configure();
        Logger = LogManager.GetLogger(this.GetType().Name);
    }
    public void AfterThrowing(MethodInfo method, Object[] args, 
		Object target, Exception exception)
    {
        if (!(exception is DbException))
            return;
        if (args != null && args.Length > 0)
        {
            foreach (Object arg in args)
            {
                Logger.Debug(""+arg);
            }
        }
    }
}

Binding Aspect and Target

As usual, we use an aspect, ExceptionHandlingAspect to organize the advice and pointcut. Note that Spring.Net doesn't have the notion of Aspect class. This is kind of a personal way to organize aspect, advices and pointcuts. ExceptionHandlingAspect has an instance variable of LogArgumentsThrowsAdvice type. The following class diagram shows the relationship.

Image 1

The GetProxy method is where interception is done. The argument methodRE is a regular expression and used to create SdkRegularExpressionMethodPointcut, which Spring.Net uses to select the methods in the target to intercept. The pointcut and advice are grouped into an advisor of type DefaultPointcutAdvisor. Note that an advisor expresses a module of an aspect. The advisor is added to the ProxyFactory. A proxy object is obtained by calling the ProxyFactory's GetProxy() method.

C#
private LogArgumentsThrowsAdvice logArgumentsThrowsAdvice;
public static object GetProxy(object target, string methodRE)
{
    SdkRegularExpressionMethodPointcut reMethodPointcut = 
			new SdkRegularExpressionMethodPointcut(methodRE);
                        
    ProxyFactory proxyFactory = new ProxyFactory(target);
    DefaultPointcutAdvisor exceptionHandlingAdvisor =
        new DefaultPointcutAdvisor(reMethodPointcut, logArgumentsThrowsAdvice);
    proxyFactory.AddAdvisor(exceptionHandlingAdvisor);
    return proxyFactory.GetProxy();
}  

If you are familiar with Spring.Net configuration file, you can abstract the above logic to the configuration file and let the Spring.Net IOC container create the proxy for you. This way, you can configure how exceptions are handled at deployment time.

Aspect in Action

The following diagram shows the aspect in action. Note the stereotype advisor. It intercepts all calls(step 1.1) to the adviced object, e.g. IOrderDAO. If the method call meets the specification of pointcut inside the advisor, it also redirects the call to its advice(step 1.2).

Image 2

The New OrderDAO

Now the exception handling code is consolidated in on advice and OrderDAO is cleaner.

C#
public class OrderDAO: IOrderDAO
{
    ...
    public void UpdateOrder(Order o)
    {
        Foo();            
    }    
    public void GetOrder(int OrderID)
    {            
        Foo2();
    }
}

Refactoring to Better Exception Handling

Now we are in a position to easily improve exception handling in our application. Let's start by looking at how we can improve the support for debugging.

What ToString returns usually doesn't have enough information for debugging. Most often than not, the important debugging information is among what doesn't get logged. OK, what if we persist the whole argument objects, instead? Since we consolidated the exception handling code in one advice, what we need to do is to create one new advice and apply the new advice. Cool, eh?

Introducing SerializeArgumentsThrowsAdvice

We all have special feeling for code. Let's get down to the code listing.

C#
/// <summary>
/// SerializeArgumentsThrowsAdvice is an around advice which
/// persists debug information.
/// 
/// </summary>
class SerializeArgumentsThrowsAdvice : IThrowsAdvice
{
    private BinaryFormatter binaryFormatter = new BinaryFormatter();
    /// <summary>
    /// Persists the target and arguments, if any, to file system.
    /// </summary>
    public void AfterThrowing(MethodInfo method, Object[] args, 
			Object target, Exception exception)
    {            
        //this.Serialize(target);     
        if(!(exception is DbException))
            return;
        if (args != null && args.Length > 0)
        {
            foreach (Object arg in args)
            {
                Serialize(arg);
            }
        }
    }
    protected virtual void Serialize(object obj)
    {
        if (obj == null)
            return;
        FileStream fileStream = null;
        try
        {
            String fileName = "" + obj.GetType() + "_" + obj.GetHashCode() + ".dat";
            fileStream = File.Create(fileName);
            binaryFormatter.Serialize(fileStream, obj);
        }
        catch (Exception e)
        {
            System.Console.WriteLine(e.StackTrace);
        }
        finally
        {
            if (fileStream != null)
            {
                fileStream.Close();
            }
        }
    }
}

It is very similar to LogArgumentsThrowsAdvice except that we call Serialize instead. Assuming arguments are marked as Serializable, Serialize(object) uses the BinaryFormatter in the namespace System.Runtime.Serialization.Formatters.Binary to persist the object onto file system.

To apply this new advice, modify the ExceptionHandlingAspect's GetProxy in the example project. Replace the logArgumentsExceptionwith serializeArgumentsThrowsAdvice. Or you can rewrite the GetProxy to make it configurable from Spring.Net configuration file.

The serialized Order objects shown in the following figure were created by the SerializeArgumentsThrowsAdvice object, which intercepts Foo.UpdateOrder(Order o) method. Now it is a matter of deserializing and bringing them back to live .NET CLR so we can investigate the internal of the Order arguments to debug the problem.

Image 3

Serializing Arguments

You may argue that what if the arguments can't be serialized. As the advice's name suggests, it serializes arguments. You may want to develop and use appropriate advice as per your needs. For example, if the properties of the arguments and the target meet your needs, you can use .NET reflection to persist the properties or use an alternative serializer such as AltSerializer. Or you can enforce implementing ToString in your design and implement an advice that persists the ToStrings.

Other Exception Handling Strategy

The Enterprise Library has an Exception Handling Block, in which you have a nice user interface to configure exception handling policy. So if you like, you can have an advice that delegates exception handling to the Exception Handling Block.

Exception inside ThrowsAdvice

Be careful when handling exception inside advice. You don't want to throw exceptions originating from within advices. They overshadow the original exceptions thrown by the target. In the example code, I simply swallowed the exception and printed out the information to console.

Conclusion

Exception handling with AOP can eliminate duplicate exception handling code, thus reducing the lines of code for exception handling and improving code maintenance. The unified exception handling increases the agility of your design. We can easily apply different exception handling with a few lines of code and configuration changes. We also showed how we can easily refactor the exception handling aspect to provide more thorough debugging information, such as the serialized argument objects of the method that throws the exception!

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)