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 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

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.

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

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

    C#
    public class RetryOnExceptionAttribute : MethodInterceptionAspect
    {
    }
  2. Define a property MaxRetries and set its default value to 3 in the constructor.

    C#
    public int MaxRetries { get; set; }
    
    public RetryOnExceptionAttribute()
    {
       this.MaxRetries  = 3;
    }
  3. Override the OnInvoke(MethodInterceptionArgs) method.

    C#
    public override void OnInvoke(MethodInterceptionArgs args)
    {
        base.OnInvoke(args);
    }
  4. Edit the OnInvoke method to catch the exception and retry the operation. Use base.OnInvoke() to invoke the intercepted method.

    The complete aspect code is as follows:

    C#
    [PSerializable]
    public class RetryOnExceptionAttribute : MethodInterceptionAspect
    {
        public int MaxRetries { get; set; }
    
        public override void OnInvoke(MethodInterceptionArgs args)
        {
            int retriesCounter = 0;
    
            while ( true )
            {
                try
                {
                    base.OnInvoke(args);
                    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);
                }
            }
        }
    }
    Note Note

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

  5. 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 will be invoked, the RetryOnExceptionAttribute.OnInvoke method will be called instead. It will invoke the original method and retry if necessary.

Accessing the identity of the intercepted method

As illustrated in the example above, you can access information about the method being intercepted from the property MethodInterceptionArgs.Method, which gives you a reflection object MethodBase. 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.

Accessing the arguments and the return value

An implementation of MethodInterceptionAspect can read and modify the arguments of the intercepted method thanks to the MethodInterceptionArgs.Arguments property, and the return value thanks to the MethodInterceptionArgs.ReturnValue property.

For example, consider a UserService class with a method that returns user permissions. The method would normally return null if no permissions have been assigned to this user.

C#
public class UserService
{
    public Permissions GetPermissions(string username)
    {
        // Database or web-service call.
    }
}

If the given user has no assigned permission, instead of returning null, we would like the method to return the permissions assigned to the user named DefaultUser.

We can implement these requirements using an aspect derived from MethodInterceptionAspect. The aspect first invokes the original method, then it checks whether it has returned a null value. If the value is null, the method is invoked again but with the arguments replaced by the default values.

To create an aspect that uses default arguments when a method returns null:

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

    C#
    [PSerializable]
    public class RetryIfNullAttribute : MethodInterceptionAspect
    {
    }
  2. Add a field defaultArguments to the class and set this field from the constructor.

    C#
    private object[] defaultArguments;
    
    public RetryIfNullAttribute(params object[] defaultArguments)
    {
        this.defaultArguments = defaultArguments;
    }
  3. Override the OnInvoke(MethodInterceptionArgs) method.

    C#
    public override void OnInvoke(MethodInterceptionArgs args)
    {
        base.OnInvoke(args);
    }
  4. Edit the OnInvoke method to implement the aspect logic. Check whether the MethodInterceptionArgs.ReturnValue property is null return value, and set the items of the MethodInterceptionArgs.Arguments property. Finally, invoke the intercepted method a second time by calling base.OnInvoke().

    The complete aspect code is as follows:

    C#
    [PSerializable]
    public class RetryIfNullAttribute : MethodInterceptionAspect
    {
        private object[] defaultArguments;
    
        public RetryIfNullAttribute(params object[] defaultArguments)
        {
            this.defaultArguments = defaultArguments;
        }
    
    
        public override void OnInvoke(MethodInterceptionArgs args)
        {
            base.OnInvoke(args);
    
            if (args.ReturnValue == null)
            {
                Console.WriteLine(
                    "The method {0}.{1} has returned a null value." +
                    " Retrying with the default arguments...",
                    args.Method.DeclaringType, args.Method.Name);
    
                for (int i = 0; i < args.Arguments.Count && i < this.defaultArguments.Length; i++)
                {
                    args.Arguments[i] = this.defaultArguments[i];
                }
    
                base.OnInvoke(args);
            }
        }
    }
  5. Apply this method interception aspect as an attribute to all methods where the implemented logic is needed. In the following snippet, the aspect is applied to the UserService.GetPermissions method:

    C#
    public class UserService
    {
        [RetryIfNull("DefaultUser")]
        public Permissions GetPermissions(string username)
        {
            // database or web-service call
        }
    }

Thanks to the aspect, whenever the UserService.GetPermissions method is invoked, the RetryIfNullAttribute.OnInvoke method will be called instead. The OnInvoke method will then invoke the original GetPermissions method. If it returns null for the provided username, then the method will be invoked again, but this time with the value DefaultValue for the username parameter.

Accessing the target objects

In combination with the parameters you will probably interact with the target code instance that the aspect is attached to. The Instance property provides you with the instance of the object that the aspect is currently operating against. It is an object type so you will need to cast it to the correct type to be able to interact with it. If you debug your aspect and that aspect doesn't make use of Instance, it will be set to null. It's also set to null if the target code is defined as static.

See Also