iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
📓

AOP in Xamarin: Using Cauldron.Interception.Fody

に公開

In my previous article, did I say that the only way to achieve AOP in Xamarin is to build your own IL manipulation library?

That was a lie. You can do it using Cauldron.Interception.Fody.

https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.nuget.org%2Fpackages%2FCauldron.Interception.Fody%2F

Oh my... well, I suppose this is good news for everyone. I, however, am deeply embarrassed.

So, in this article, I would like to introduce how to perform AOP in Xamarin (specifically, how to intercept method calls) using Cauldron.Interception.Fody.

Sample Overview

We will create a very simple sample. It will call a method on the MainPage and simply log the method call using Debug.WriteLine.

In a real-world project, this could be used for:

  • Handling anticipated exceptions (e.g., communication errors)
  • Transaction management
  • Authentication and authorization processes
  • Logging (or perhaps analyzing features frequently used in things like Google Analytics? I'm not entirely sure)

You can use it to weave these aspects into your code uniformly.

It's quite useful, don't you think?

Preparation

Please follow the steps below to prepare your environment.

  1. Create a new Xamarin.Forms project (I named it XFodyApp here).
  2. Apply the Cauldron.Interception.Fody NuGet package (applying it only to the shared code project is sufficient).
    NuGet Gallery | Cauldron.Interception.Fody 2.0.16
  3. Create a FodyWeavers.xml file in the XFodyApp project (the shared code project).

Add the following content to FodyWeavers.xml:

<?xml version="1.0" encoding="utf-8" ?>
<Weavers>
  <Cauldron.Interception/> 
</Weavers>

That completes the preparation.

Creating an Interceptor

Next, we will create a class to intercept method calls and output logs.

Let's create an InterceptorAttribute class and implement it as follows:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class InterceptorAttribute : Attribute, IMethodInterceptor
{
    private MethodBase _methodbase;
    public void OnEnter(Type declaringType, object instance, MethodBase methodbase, object[] values)
    {
        _methodbase = methodbase;
        Debug.WriteLine($"OnEnter() declaringType:{declaringType} instance:{instance} methodbase.Name:{_methodbase.Name} args:{string.Join(", ", values)}");
    }

    public void OnException(Exception e)
    {
    }

    public void OnExit()
    {
        Debug.WriteLine($"OnExit() methodbase.Name:{_methodbase.Name}");
    }
}

This is the attribute class that intercepts method calls.

Although it only outputs logs to the Debug console, it serves our purpose.

Weaving the Interceptor

It's a bit rough, but let's add a method to MainPage.xaml.cs and weave the interceptor we just created into it.

Implement MainPage.xaml.cs as follows:

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
        Add(1, 2);
    }

    [Interceptor]
    private int Add(int left, int right)
    {
        var result = left + right;
        Debug.WriteLine($"MainPage#Add() result:{result}");
        return result;
    }
}

We have declared Interceptor on the Add method.
Running this will display the following in the debug window:

As you can see, it is properly intercepted!
By the way, it is said that the IL of the method woven with the interceptor is edited to be equivalent to the following code:

public int Add()
{
    InterceptorAttribute interceptorAttribute = new InterceptorAttribute("Any valid attribute parameter types");

    try
    {
        interceptorAttribute.OnEnter(typeof(MainPage), this, MethodBase.GetMethodFromHandle(methodof(MainPage.Add()).MethodHandle, typeof(MainPage).TypeHandle), new object[0]);
        var result = left + right;
        Debug.WriteLine($"MainPage#Add() result:{result}");
        return result;
    }
    catch (Exception e)
    {
        interceptorAttribute.OnException(e);
        throw;
    }
    finally
    {
        interceptorAttribute.OnExit();
    }
}

Well, now you can enjoy the AOP life in Xamarin to your heart's content... or so I wish.

Actually, I'm not quite happy with the specifications

To be honest, I'm not really fond of the interceptor specifications.

The interceptor implements the three methods OnEntry, OnExit, and OnException as follows:

public class InterceptorAttribute : Attribute, IMethodInterceptor
{
    public void OnEnter(Type declaringType, object instance, MethodBase methodbase, object[] values)
    {
    }

    public void OnExit()
    {
    }

    public void OnException(Exception e)
    {
    }
}

While it is certainly possible to implement things this way, I have three main complaints:

  1. I want a mechanism where multiple interceptors can be chained and applied, similar to the Chain of Responsibility pattern.
  2. I want to intercept using a mechanism like Castle.Core's DynamicProxy.
  3. It is not possible to catch an exception, perform cleanup, and suppress it from bubbling up.

By the way, for point 2, I would like to implement something like this:

public class Interceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        using(var connection = new SQLConnection(...))
        {
            connection.Open();
            invocation.Proceed();
        }
    }
}

Although there is the easy-to-solve issue of how to pass the connection to the target location, I have a desire to manage transactions within the interceptor and use using statements properly like this.

Of course, it is not impossible to do this even if the pre-processing and post-processing are called in separate methods, but at the very least, Cauldron.Interception.Fody does not allow for implementing multiple cross-cutting concerns by combining single-purpose interceptors. Also, as mentioned in point 3, you cannot implement logic to swallow exceptions.

If it's not possible...

I guess I have to build it myself!

So, I hope to have something ready by my personal deadline for the Advent Calendar on the 25th. But I don't know much about IL at all... I wonder if I'll make it in time.

Anyway, that's all for today.

See you next time!

Discussion