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(){ }


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


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

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

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

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


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");


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.

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


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()
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);
    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()
    foreach (var method in GetMethods())

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

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


No comments yet. Be the first!

No new comments are allowed on this post.