Simple AOP with Fody

How to setup Fody for "In Solution" weaving

First off some admin to get you started on Fody. Skip to "The AOP" if you already know this part.

Enable Fody

First off install the Fody VSIX.

Select the target project and use the top level menu. Project > Fody > Configure.

Add a Weavers project to your solution

  • Add a Class Library named Weavers to your solution.
  • Set the solution build order so Weavers builds first.
  • Add reference to Mono.Cecil.dll. It will exists in $(SolutionDir)Tools\Fody\

Add a ModuleWeaver class

For the purposes of this example only the ModuleDefinition needs to be injected.

public class ModuleWeaver
{
    public ModuleDefinition ModuleDefinition { get; set; }

    public void Execute(){ }
}

The AOP

In simplistic terms Aspect Oriented Programming can be expressed in three parts

  • Target: The original code base to be modified.
  • Result: The desired behavior of the code once it has been modified
  • Location: Where we want to do this modification
  • Modification: The code we use to do the modification

Target

In this example the code base I am beginning with is a simple console application.

class Program
{
    static void Main()
    {
        Debug.WriteLine("Calling Method1");
        Method1();
        Debug.WriteLine("Calling Method2");
        Method2();
    }

    static void Method1()
    {
        Debug.WriteLine("Code inside Method1");
    }

    static void Method2()
    {
        Debug.WriteLine("Code inside Method2");
    }
}

Result

In this example I want to write, to the debug log, a timestamp when a method is entered. I also want to concatenation that timestamp with the method name. So, for example, when applied to Method1 the below line would be added at the start of the method.

Debug.WriteLine(DateTime.Now.ToLongTimeString() + " Method1");

Location

In this example I am going to use an attribute to define which methods to inject the timestamp code.

class LogMethodEntryAttribute : Attribute
{
}

And add this to a method.

[LogMethodEntry]
static void Method1()
{
    Debug.WriteLine("Code inside Method1");
}

Modification

This is where we get to manipulate the IL.

What IL we want to inject

You need to know what IL you are injecting. So this code

Debug.WriteLine(DateTime.Now.ToLongTimeString() + " MethodName");

Equates to this IL

call valuetype [mscorlib]System.DateTime [mscorlib]System.DateTime::get_Now()
stloc.0 
ldloca.s 
call instance string [mscorlib]System.DateTime::ToLongTimeString()
ldstr " MethodName"
call string [mscorlib]System.String::Concat(string, string)
call void [System]System.Diagnostics.Debug::WriteLine(string)

Yes 1 line of c# creates 7 lines of IL.

Some Cecil references

Note the above IL makes use of several types and members. To inject code that references types and members you need to tell Cecil where to find them. So add some fields so these references can be re-used and then instantiate them.

MethodReference nowMethod;
MethodReference toLongTimeStringMethod;
TypeReference dateTimeType;
MethodReference concatMethod;
MethodReference writLineMethod;

void InitialiseReferences()
{
    dateTimeType = ModuleDefinition.Import(typeof (DateTime));
    var dateTimeDefinition = dateTimeType.Resolve();
    nowMethod = ModuleDefinition.Import(dateTimeDefinition.Methods.First(x => x.Name == "get_Now"));
    toLongTimeStringMethod = ModuleDefinition.Import(dateTimeDefinition.Methods.First(x => x.Name == "ToLongTimeString"));
    var stringType = ModuleDefinition.Import(typeof(string)).Resolve();
    concatMethod = ModuleDefinition.Import(stringType.Methods.First(x => x.Name == "Concat" && x.Parameters.Count == 2));
    var debugType = ModuleDefinition.Import(typeof(Debug)).Resolve();
    writLineMethod = ModuleDefinition.Import(debugType.Methods.First(x => x.Name == "WriteLine" && x.Parameters.Count == 1 && x.Parameters[0].ParameterType.Name == "String"));
}

Add a call to InitialiseReferences in your Execute method.

Injecting the IL

This is fairly reflective of the IL listed above. However note the us of the MethodDefinition.Name when defining the string.

void Inject(MethodDefinition method)
{
    var instructions = method.Body.Instructions;
    var variableDefinition = new VariableDefinition(dateTimeType);
    method.Body.Variables.Add(variableDefinition);
    instructions.Insert(0, Instruction.Create(OpCodes.Call, nowMethod));
    instructions.Insert(1, Instruction.Create(OpCodes.Stloc_0));
    instructions.Insert(2, Instruction.Create(OpCodes.Ldloca_S, variableDefinition));
    instructions.Insert(3, Instruction.Create(OpCodes.Call, toLongTimeStringMethod));
    instructions.Insert(4, Instruction.Create(OpCodes.Ldstr, " " + method.Name));
    instructions.Insert(5, Instruction.Create(OpCodes.Call, concatMethod));
    instructions.Insert(6, Instruction.Create(OpCodes.Call, writLineMethod));
}

Find methods with the attribute

To find methods with the LogMethodEntryAttribute use the following.

IEnumerable<MethodDefinition> GetMethods()
{
    return ModuleDefinition.Types.SelectMany(x => x.Methods.Where(ContainsAttribute));
}

bool ContainsAttribute(MethodDefinition method)
{
    return method.CustomAttributes.Any(x => x.Constructor.DeclaringType.FullName == "LogMethodEntryAttribute");
}

Putting it together

With the above pieces strung together the Execute method will look like this.

public void Execute()
{
    InitialiseReferences();
    foreach (var method in GetMethods())
    {
        Inject(method);
    }
}

The end result

The Code

After a build Method1 will look like this in a decompiler.

static void Method1()
{
    Debug.WriteLine(DateTime.Now.ToLongTimeString() + " Method1");
    Debug.WriteLine("Code inside Method1");
}

The Output

When you run the output to your debug window will be.

Calling Method1
11:10:39 PM Method1
Code inside Method1
Calling Method2
Code inside Method2

In Summary

So there a fair number of moving pieces here. However most of them are once offs "just to get you started" things. The actual ModuleWeaver class is 60 lines of code and can be made much smaller with some simple extension code to Cecil.

As you can see this approach is very powerful. Anything you can dream up, that can be expressed in IL, can be done with this kind of code.

The Source

Full source available here https://github.com/SimonCropp/Experiments/tree/master/SimpleAOPwithFody

Posted by: Simon Cropp
Last revised: 08 Feb, 2013 11:13 PM History

Comments

Richard Collette
Richard Collette
17 Feb, 2012 03:17 PM @ version 4

The bane of .NET AOP has always been that either an interface or virtual methods are required when using the dynamic proxy method or they tend to rely on attributes for weaving. It will be interesting if someone can take Fody's weaving capability and extend it to a full join point model implementation (i.e. configuration with pattern matching).

Simon Cropp
Simon Cropp
19 Feb, 2012 05:03 AM @ version 4

Richard

While it is of course possible to build a "full join point model implementation" on top of Fody I am not sure how valuable that would be. I prefer the approach of specific targeted solutions that use convention over configuration.

Take a look at the addins for inotifypropertychanged and inotifypropertychanging. because they are solving a specific problem they can take a step further and provide much more value than a generic point cut implementation. For example look at these features http://code.google.com/p/propertychanged/wiki/ImplementingAnIsChangedFlag http://code.google.com/p/propertychanged/wiki/PropertyDependencies

A point cut approach, I think, abstracts you too much from the problem you are trying to solve.

No new comments are allowed on this post.