This is the online documentation for PostSharp 5.0.
Download PDF or CHM. Go to v4.3 or v5.0

Intercepting Methods

It is often useful to be able to intercept the invocation of a method and invoke your own hook in its place. Common use cases for this capability include dispatching the method execution to a different thread, asynchronously executing the method at a later time, and retrying the method call an when exception is thrown.

PostSharp addresses these needs with the MethodInterceptionAspect aspect class which intercepts the invocation of a method before the method is executed. It also allows you to invoke the original method and access its arguments and return value.

The current article covers method interception, for another approach to injecting behaviors into methods, see Injecting Behaviors Before and After Method Execution.

This topic contains the following sections:

Intercepting a method call

To create an aspect that retries a method call on exception:

  1. Add a reference to the PostSharp package to your project.

  2. Create an aspect class and inherit MethodInterceptionAspect. Annotate the class with the [PSerializableAttribute] custom attribute.

  3. Override and implement the OnInvoke(MethodInterceptionArgs) method and implement the interception logic. Call args.Proceed() to invoke the intercepted method.

    Note Note

    Calling base.OnInvoke() is equivalent to calling args.Proceed().

  4. Add the aspect to one or more methods. Since MethodInterceptionAspect 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

Consider the following CustomerService class which has methods to load and save customer entities and relies on calls to a database or a web-service.

C#
public class CustomerService
{
    public void Save(Customer customer)
    {
        // Database or web-service call.
    }

}

Occasionally, the connection to the underlying store may become unreliable and the application user is presented with the error message. To improve the user experience you may want to retry the failing operation several times before displaying the error message. In the following steps, we'll create a method interception class which can be applied to repository methods and will retry the invocation whenever an exception is thrown by the original method.

The complete aspect code is as follows:

C#
[PSerializable]
public class RetryOnExceptionAttribute : MethodInterceptionAspect
{
    public RetryOnExceptionAttribute()
    {
        this.MaxRetries = 3;
    }

    public int MaxRetries { get; set; }

    public override void OnInvoke(MethodInterceptionArgs args)
    {
        int retriesCounter = 0;

        while (true)
        {
            try
            {
                args.Proceed();
                return;
            }
            catch (Exception e)
            {
                retriesCounter++;
                if (retriesCounter > this.MaxRetries) throw;

                Console.WriteLine(
                    "Exception during attempt {0} of calling method {1}.{2}: {3}",
                    retriesCounter, args.Method.DeclaringType, args.Method.Name, e.Message);
            }
        }
    }
}

Apply the [RetryOnException] custom attributes to all methods where the behavior is needed.

In the following snippet, this aspect is applied to the CustomerService.Save method:

C#
public class CustomerService
{
    [RetryOnException(MaxRetries = 5)]
    public void Save(Customer customer)
    {
        // Database or web-service call.
    }
}

Whenever the CustomerService.Save method is invoked, the RetryOnExceptionAttribute.OnInvoke method is called instead. The aspect method will invoke the original method and retry if necessary.

Accessing the current execution context

As illustrated in the example above, you can access information about the method being intercepted from the property MethodInterceptionArgs.Method, which gives you the MethodBase of the method that has been intercepted. This object gives you access to parameters, return type, declaring type, and other characteristics. In case of generic methods or generic types, MethodInterceptionArgs.Method gives you the proper generic method instance, so you can use this object to get generic parameters.

The MethodInterceptionArgs parameter gives you access to other pieces of information regarding the current execution context.

Property

Description

Method

The method being executed (in case of generic methods, this property is set to the proper generic instance of the method).

Arguments

The arguments passed to the method. If you modify the arguments and call args.Proceed(), the intercepted method will be invoked with the modified arguments.

Instance

The object on which the method is being executed, i.e. the value of the this keyword.

ReturnValue

The return value of the method. This property is populated after the aspect calls args.Proceed. The aspect can then modify the value of this property if it needs to return a different value than the one returned by the intercepted method.

Intercepting methods returning a Task, including async methods

The Task class in .NET represents operations that can execute asynchronously. Whenever you want to intercept a method that returns a Task, you have two options of how to define the target of the interception:

  • Intercepting the logic that creates and returns a new Task. The logic of the asynchronous operation represented by the Task is not intercepted, and the Task's status, return value and thrown exception are not handled by an aspect. This is what happens when you intercept a Task-returning method with an aspect implementing only OnInvoke(MethodInterceptionArgs).

  • Intercepting both the logic that instantiates the Task and the logic of the Task. In this case, you intercept the asynchronous operation represented by the task. You can await for the completion of the task, and you can handle the task's return value and thrown exception inside the aspect. This interception mode is called semantic advising.

To intercept the whole Task logic, your aspect must implement the OnInvokeAsync(MethodInterceptionArgs) method. Your implementation can call args.ProceedAsync() instead of args.Proceed() to invoke the intercepted method and execute the intercepted task.

When an aspect implements both OnInvoke(MethodInterceptionArgs) and OnInvokeAsync(MethodInterceptionArgs), PostSharp automatically selects the proper method when the target method returns a Task. You can change this behavior by changing the value of the SemanticallyAdvisedMethodKinds aspect property. See Semantic Advising of Iterator and Async Methods for details regarding this property.

In this article, we will demonstrate how to control the semantic behavior of the MethodInterceptionAspect aspect when it is applied to a method returning a task or to an async method. For a more general information about using MethodInterceptionAspect see Intercepting Methods.

Example

To demonstrate when the semantic approach to method interception can be useful, let's extend at the RetryOnExceptionAttribute aspect created in the previous example.

The previous version of RetryOnExceptionAttribute aspect intercepts the original method, but it does not intercept the async task returned by the method. As a result, the aspect cannot catch the exception thrown by the async task and the test method fails. In order to properly handle async methods, we need to add an implementation of the OnInvokeAsync(MethodInterceptionArgs) method to the RetryOnExceptionAttribute aspect:

C#
[PSerializable]
public class RetryOnExceptionAttribute : MethodInterceptionAspect
{
    public RetryOnExceptionAttribute()
    {
        this.MaxRetries = 3;
    }

    public int MaxRetries { get; set; }

    public override void OnInvoke(MethodInterceptionArgs args)
    {
        int retriesCounter = 0;

        while (true)
        {
            try
            {
                args.Proceed();
                return;
            }
            catch (Exception e)
            {
                retriesCounter++;
                if (retriesCounter > this.MaxRetries) throw;

                Console.WriteLine(
                    "Exception during attempt {0} of calling method {1}.{2}: {3}",
                    retriesCounter, args.Method.DeclaringType, args.Method.Name, e.Message);
            }
        }
    }

    public override async Task OnInvokeAsync(MethodInterceptionArgs args)
    {
        int retriesCounter = 0;

        while (true)
        {
            try
            {
                await args.ProceedAsync();
                return;
            }
            catch (Exception e)
            {
                retriesCounter++;
                if (retriesCounter > this.MaxRetries) throw;

                Console.WriteLine(
                    "Exception during attempt {0} of calling method {1}.{2}: {3}",
                    retriesCounter, args.Method.DeclaringType, args.Method.Name, e.Message);
            }
        }
    }
}

We can now apply the [RetryOnException] aspect to an async method:

C#
[RetryOnException]
private async Task AsyncThrow()
{
    await Task.Yield();
    if (--this.counter > 0)
    {
        throw new Exception();
    }
}

Whenever the AsyncThrow method is invoked, the RetryOnExceptionAttribute.OnInvokeAsync method is called instead. The aspect method will invoke the original method asynchronously and await for its completion. If the asynchronous task throws an exception, the aspect will catch the exception and either retry the asynchronous call or re-throw the exception.

See Also