CrossCutterN: A Light Weight AOP Tool for .NET

0
55

Introduction

As AOP has become a well known and exercised concept in programming, developers have beome more and more dependent on proper AOP tools.

In .NET programming, the best known AOP tool would be PostSharp, which allows injection of custom AOP code using custom attributes. As good things always don’t come free, the limitation of PostSharp express makes developers who concern about their project scale hesitate to use it, and  the price of ultimate version would become a major concern of quite some developers.

Another concern about PostSharp style AOP is the license acquiring and added dependency. The manual license acquiring for PostSharp stops a fresh checkout of a project from being successfully built before it’s done.

To have a free and light weight AOP tool for .NET, CrossCutterN is introduced. It allows developers to inject custom AOP code into methods/properties of classes via custom attributes and method/property names, is free to use and update, and can release projects from compile time dependencies on AOP code.

Background

This article assumes that reader are familiar with the concept of Aspect Orinted Prgramming, and perhaps has some previous experience using AOP frameworks like PostSharp, Spring AOP and so on.

Using the code

Let’s take some simple code as an example:

For the following implementation to be built into assembly CrossCutterN.SampleTarget.exe:

namespace CrossCutterN.SampleTarget
{
    using System;

    class Target
    {
        public static int Add1(int x, int y)
        {
            Console.Out.WriteLine("Inside Add1, starting");
            var z = x + y;
            Console.Out.WriteLine("Inside Add1, ending");
            return z;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Target.Add1(1, 2);
        }
    }
}

To have method “Add1” outputing it’s function name and parameter values upon entry, and output it’s return value right before returning, the following can be done:

  1. Implement customized attribute and AOP code (referred to as advices in the following content) in a separate assembly CrossCutterN.SampleAdvice which relies on CrossCutterN.Advice and CrossCutterN.Concern assemblies:
namespace CrossCutterN.SampleAdvice
{
    using System;
    using System.Text;
    using Advice.Parameter;
    using Concern.Attribute;

    public sealed class SampleConcernMethodAttribute : MethodConcernAttribute
    {
    }

    public static class Advices
    {
        public static void OnEntry(IExecution execution)
        {
            var strb = new StringBuilder(execution.Name);
            strb.Append("(");
            if (execution.Parameters.Count > 0)
            {
                foreach (var parameter in execution.Parameters)
                {
                    strb.Append(parameter.Name).Append("=").Append(parameter.Value).Append(",");
                }
                strb.Remove(strb.Length - 1, 1);
            }
            strb.Append(")");
            Console.Out.WriteLine("Entry at {0}: {1}", DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss.fff tt"), strb.ToString());
        }

        public static void OnExit(IReturn rReturn)
        {
            if (rReturn.HasReturn)
            {
                Console.Out.WriteLine("Exit at {0}: returns {1}", DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss.fff tt"), rReturn.Value);
            }
            else
            {
                Console.Out.WriteLine("Exit at {0}: no return", DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss.fff tt"));
            }
        }
    }
}
  1. Apply the attribute to the method to be injected:
namespace CrossCutterN.SampleTarget
{
    using System;
    using SampleAdvice;

    class Target
    {
        [SampleConcernMethod]
        public static int Add1(int x, int y)
        {
            Console.Out.WriteLine("Inside Add1, starting");
            var z = x + y;
            Console.Out.WriteLine("Inside Add1, ending");
            return z;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Target.Add1(1, 2);
            Target.Add2(1, 2);
        }
    }
}
  1. Copy both CrossCutterN.SampleTarget.exe and CrossCutterN.SampleAdvice assemblies to CrossCutterN.Command directory.
  2. In windows command prompt, navigate to CrossCutterN.Command folder, execute the following command to load assembly CrossCutterN.SampleTarget.exe and output it as CrossCutterN.SampleTarget_Weaved.exe.
CrossCutterN.Command.exe CrossCutterN.SampleTarget.exe CrossCutterN.SampleTarget_Weaved.exe

Note that the in CrossCutterN.Command.exe.config file, the following configuration tells CrossCutterN.Command.exe to inject advices in CrossCutterN.SampleAdvice.dll into assembly CrossCutterN.SampleTarget.exe:

<concernAttributeAspectBuilders>
  <add id="Builder1">
    <factoryMethod type="CrossCutterN.Aspect.Builder.AspectBuilderFactory, CrossCutterN.Aspect"
                   method="InitializeConcernAttributeAspectBuilder"
                   methodConcernAttribute="CrossCutterN.SampleAdvice.SampleConcernMethodAttribute, CrossCutterN.SampleAdvice"/>
    <pointcut>
      <add joinPoint="Entry" sequence="1"
           classType="CrossCutterN.SampleAdvice.Advices, CrossCutterN.SampleAdvice" method="OnEntry" parameterPattern="Execution"/>
      <add joinPoint="Exit" sequence="1"
           classType="CrossCutterN.SampleAdvice.Advices, CrossCutterN.SampleAdvice" method="OnExit" parameterPattern="Return"/>
    </pointcut>
  </add>
</concernAttributeAspectBuilders>
  1. Execute CrossCutterN.SampleTarget.exe and CrossCutterN.SampleTarget_Weaved.exe, the difference suggests that the advices have been injected:

Further more, this AOP code injection can be done using method name matching:

For the following implementation in CrossCutterN.SampleTarget.exe:

namespace CrossCutterN.SampleTarget
{
    using System;
    using SampleAdvice;

    class Target
    {
        public static int Add2(int x, int y)
        {
            Console.Out.WriteLine("Inside Add2, starting");
            var z = x + y;
            Console.Out.WriteLine("Inside Add2, ending");
            return z;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Target.Add2(1, 2);
        }
    }
}

We use the same advice methods only depends on CrossCutterN.Advice assembly, without declaring any custom attribute:

namespace CrossCutterN.SampleAdvice
{
    using System;
    using System.Text;
    using Advice.Parameter;

    public static class Advices
    {
        public static void OnEntry(IExecution execution)
        {
            var strb = new StringBuilder(execution.Name);
            strb.Append("(");
            if (execution.Parameters.Count > 0)
            {
                foreach (var parameter in execution.Parameters)
                {
                    strb.Append(parameter.Name).Append("=").Append(parameter.Value).Append(",");
                }
                strb.Remove(strb.Length - 1, 1);
            }
            strb.Append(")");
            Console.Out.WriteLine("Entry at {0}: {1}", DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss.fff tt"), strb.ToString());
        }

        public static void OnExit(IReturn rReturn)
        {
            if (rReturn.HasReturn)
            {
                Console.Out.WriteLine("Exit at {0}: returns {1}", DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss.fff tt"), rReturn.Value);
            }
            else
            {
                Console.Out.WriteLine("Exit at {0}: no return", DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss.fff tt"));
            }
        }
    }
}

In CrossCutterN.Command.exe.config file, the following part tells CrossCutterN.Command.exe to inject the AOP codes into “Add2” Method:

<nameExpressionAspectBuilders>
  <add id="Builder2">
    <factoryMethod type="CrossCutterN.Aspect.Builder.AspectBuilderFactory, CrossCutterN.Aspect"
                   method="InitializeNameExpressionAspectBuilder">
      <includes>
        <add expression="CrossCutterN.SampleTarget.Target.Add2"/>
      </includes>
    </factoryMethod>
    <pointcut>
      <add joinPoint="Entry" sequence="2"
           classType="CrossCutterN.SampleAdvice.Advices, CrossCutterN.SampleAdvice" method="OnEntry" parameterPattern="Execution"/>
      <add joinPoint="Exit" sequence="2"
           classType="CrossCutterN.SampleAdvice.Advices, CrossCutterN.SampleAdvice" method="OnExit" parameterPattern="Return"/>
    </pointcut>
  </add>
</nameExpressionAspectBuilders>

Execute the same windows command under CrossCutterN.Command directory:

CrossCutterN.Command.exe CrossCutterN.SampleTarget.exe CrossCutterN.SampleTarget_Weaved.exe

Execute the exe assemblies separately, the result is similar:

Quite a lot of AOP features can be implemented and injected this way, like AOP logging, AOP authorization, AOP caching, AOP parameter verification, and so on.

The above is just a simple demonstration of CrossCutterN. CrossCutterN tool can inject methods and properties at points of entry, exception and exit with various of configuration options. It is much more flexible, configurable and extendable than the features introduced above. Interested reader please visit GitHub to find out more details about CrossCutterN and download the source code.

Points of Interest

CrossCutterN depends on Mono.Cecil for IL weaving. For a IL beginner to quickly understand how to write IL code, writing some simple code in C# and opening the compiled assembly using ILdasm.exe is the fastest way.

History

  • Initialized version.

LEAVE A REPLY