Adding exception handlers to code requires the addition of try/catch
statements which can quickly pollute code. Exception handling implemented this way is also not reusable, requiring the same logic to be implemented over and over wherever exceptions must be dealt with. Raw exceptions also present cryptic information and can often expose too much information to the user.
PostSharp provides a solution to these problems by allowing custom exception handling logic to be encapsulated into a reusable class, which is then easily applied as an attribute to all methods and properties where exceptions are to be dealt with.
Intercepting an exception
PostSharp provides the OnExceptionAspect class which is the base class from which exception handlers are to be derived from.
If you also need to execute logic before and upon success of the target method, you can derive your aspect from the OnMethodBoundaryAspect class. Both classes behave almost identically as far as exception handling is concerned.
Both classes define a virtual method named OnException(MethodExecutionArgs) method: this is the method where the exception handling logic (i.e. what would normally be in a catch
statement) goes. A MethodExecutionArgs parameter is passed into this method by PostSharp; it contains information about the exception.
To create an exception handling aspect:
Add a reference to the PostSharp package to your project.
Derive a class from OnExceptionAspect.
Apply the PSerializableAttribute to the class.
Override OnException(MethodExecutionArgs) and implement your exception handling logic in this class. The Exception object is available on the Exception property of the MethodExecutionArgs parameter.
Add the aspect to one or more methods. Since OnExceptionAspect derives from the Attribute class, you can just add the aspect custom attribute to the methods you need. If you need to add the aspect to more methods (for instance all public methods in a namespace), you can learn about more advanced techniques in Adding Aspects to Code.
Example
The following snippet shows an example of an exception handler which watches for exceptions of any type, and then writes a message to the console when an exception occurs:
[PSerializable]
public class PrintExceptionAttribute : OnExceptionAspect
{
public override void OnException(MethodExecutionArgs args)
{
Console.WriteLine(args.Exception.Message);
}
}
Once created, apply the derived class to all methods and/or properties for which the exception handling logic is to be used, as shown in the following example:
class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
[PrintException]
public void StoreName(string path)
{
File.WriteAllText( path, string.Format( "{0} {1}", this.FirstName, this.LastName ) );
}
}
Here PrintException
will output a message when an exception occurs in trying to write text to a file.
Specifying the type of handled exceptions
The GetExceptionType(MethodBase) method can be used to return the type of the exception which is to be handled by this aspect. Otherwise, all exceptions will be caught and handled by this class. Note that the GetExceptionType(MethodBase) method is evaluated at build time.
If the aspect needs to handle several types of exception, the GetExceptionType
should return a common base type, and the OnException
implementation should be modified to dynamically handle different types of exception.
Example
In the following snippet, we updated the PrintExceptionAttribute
aspect and added the possibility to specify from the custom attribute constructor which type of exception should be traced.
[PSerializable]
public class PrintExceptionAttribute : OnExceptionAspect
{
Type type;
public PrintExceptionAttribute(Type type)
{
this.type = type;
}
// Method invoked at build time.
// Should return the type of exceptions to be handled.
public override Type GetExceptionType(MethodBase method)
{
return this.type;
}
public override void OnException(MethodExecutionArgs args)
{
Console.WriteLine(args.Exception.Message);
}
}
class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
[PrintException(typeof(IOException)]
public void StoreName(string path)
{
File.WriteAllText( path, string.Format( “{0} {1}”, this.FirstName, this.LastName ) );
}
}
Ignoring ("swallowing") exceptions
The FlowBehavior member of MethodExecutionArgs in the exception handler’s OnException(MethodExecutionArgs) method, can be set to ignore an exception. Note however that ignoring exceptions is generally dangerous and not recommended. In practice, it’s only safe to ignore exceptions in event handlers (e.g. to display a message in a WPF form) and in thread entry points.
Exceptions can be ignored by setting the FlowBehavior property to Return
. You must then set the return value of the method by setting the ReturnValue property.
Example
The following aspect catches all exceptions flowing from the methods the aspect is applied to, prints the exception to the console, and makes the target method return -1
instead of failing with an exception.
[PSerializable]
public class PrintAndIgnoreExceptionAttribute : OnExceptionAspect
{
public override void OnException(MethodExecutionArgs args)
{
Console.WriteLine(args.Exception.Message);
args.FlowBehavior = FlowBehavior.Return;
args.ReturnValue = -1;
}
}
public class Customer
{
[PrintException(typeof(IOException))]
public int GetDataLength(string path)
{
return File.ReadAllText(path).Length;
}
}
Replacing or wrapping exceptions
Many times, the original exception must be hidden from the user or the client of the service, and should be replaced by another exception.This can be done by setting the FlowBehavior property to FlowBehavior.ThrowException
and the Exception to the new exception.
FlowBehavior.RethrowException
: rethrows the original exception after the exception handler exits. This is the default behavior for the OnException(MethodExecutionArgs) advice.FlowBehavior.ThrowException
: throws a new exception once the exception handler exits. This is useful when details of the original exception should be hidden from the user or when a more meaningful exception is to be shown instead. When throwing a new exception, a new exception object must be assigned to theException
member of MethodExecutionArgs. The following snippet shows the creation of a newBusinessExceptionAttribute
which throws aBusinessException
containing a description of the cause:[PSerializable] public sealed class BusinesssExceptionAttribute : OnExceptionAspect { public override void OnException(MethodExecutionArgs args) { args.FlowBehavior = FlowBehavior.ThrowException; args.Exception = new BusinessException("Bad Arguments", new Exception("One or more arguments were null. Use the id " + guid.ToString() + " for more information")); } }
Note
You can also throw a new exception from the OnException(MethodExecutionArgs) method, but the exception call stack will show the aspect method itself, while with FlowBehavior, the exception call stack will originate from the target method (unless there is an interception aspect on the target method, in which case the call stack will originate from an intermediate method).
Example
The following aspect handles all exceptions in the BusinessServices
class by generating a GUID for it, writing all details to the trace file and then throwing a BusinessException
showing just the incident GUID and hiding other details.
[PSerializable]
public sealed class HandleExceptionsAttribute : OnExceptionAspect
{
public override void OnException(MethodExecutionArgs args)
{
Guid guid = Guid.NewGuid();
// In a real-world app, we would file the exception in the QA database.
Trace.WriteLine( #"Exception {guid}:");
Trace.WriteLine(args.Exception.ToString());
args.FlowBehavior = FlowBehavior.ThrowException;
args.Exception = new BusinessException( $"The service failed unexpectedly. Please report the incident to the QA team with the id #{guid}." );
}
}
[HandleExceptions]
public class BusinessServices
{
// Dozens of methods here.
}
public class BusinessException : Exception
{
public BusinessException(string message) : base(message)
{
}
}
Accessing the current execution context
The OnException(MethodExecutionArgs) method requires one argument of type MethodExecutionArgs. This object gives access to the exception being handled, the identity of the method being executed, its arguments, and the current object.
The following table lists the pieces of context made available by the MethodExecutionArgs class.
Property | Description |
---|---|
Method | The method or constructor being executed (in case of generic methods, this property is set to the proper generic instance of the method). This is not necessarily equal to the method that originally threw the exception. |
Arguments | The arguments passed to the method. In case of out and ref arguments, the argument values can be modified by the aspect. |
Instance | The object on which the method is being executed, i.e. the value of the this keyword. |
Exception | The Exception thrown by the method. This value can be modified (see below). |
Note
The properties of the MethodExecutionArgs class cannot be directly viewed in the debugger. Because optimizations, the properties must be referenced in your source code in order to be viewable in the debugger.
Example
The following code improves the previous example by adding more context information to the print-out of the exception details.
[PSerializable]
public sealed class HandleExceptionsAttribute : OnExceptionAspect
{
public override void OnException(MethodExecutionArgs args)
{
Guid guid = Guid.NewGuid();
// In a real-world app, we would file the exception in the QA database.
Trace.WriteLine( $"Exception {guid} when invoking the method {args.Method.DeclaringType.FullName}.{args.Method.Name}({string.Join(", ", args.Arguments)}) ");
Trace.WriteLine(args.Exception.ToString());
args.FlowBehavior = FlowBehavior.ThrowException;
args.Exception = new BusinessException( $"The service failed unexpectedly. Please report the incident to the QA team with the id #{guid}." );
}
}
See Also
Reference
OnException(MethodExecutionArgs)
MethodExecutionArgs
OnExceptionAspect
PSerializableAttribute
GetExceptionType(MethodBase)
Other Resources