[Related Article: Introduction to Aspect Oriented Programming and PostSharp]

There is undocumented functionality in PostSharp 2.1 (2.1.2.3 or higher) that allows us to apply aspects to assemblies that we don’t have the source code for. I’m going to show you how this works, but first I have state that this functionality is undocumented because it isn’t officially supported by PostSharp. Proceed at your own risk.

Setup

If you do not have PostSharp 2.1.2.3 (at this point it’s CTP 3) then get it and install it. We will create two projects, a console project that we’ll be using as our dummy and a class library that is going to hold our aspect provider. I highly recommend that you read through the PostSharp Principals series if you are not familiar with PostSharp or aspect providers.

We will also have a folder called ‘Lab’ which we’ll store all our needed files in. Create the Lab folder and then create a subfolder under Lab called Output.

Note: I’m targeting .NET 4.0.

Code

The proceeding code is going to be our dummy executable that we will apply aspects to. Go ahead and start a new console project in Visual Studio called DummyApp. No need to reference PostSharp or any other assemblies. Replace the code in Program.cs with the following:

namespace DummyApp
{
    class Program
    {
        static void Main(string[] args)
        {
            ExampleClass ec = new ExampleClass();

            ec.Method1();
            ec.Method2();

            Console.ReadKey();
        }
    }

    public class ExampleClass
    {
        public void Method1()
        {
            Console.WriteLine("Method1()");
        }

        public void Method2()
        {
            Console.WriteLine("Method2()");
        }
    }
}

Build the project and copy DummyApp.exe to the Lab folder.

Now for the aspect provider. Create a new class library project and call it MyProviders. Add a reference to PostSharp. Add a new class file and name it TraceAspect.cs. Add the following code:

[Serializable]
public class SomeTracingAspect : IOnMethodBoundaryAspect
{
    public void OnEntry(MethodExecutionArgs args)
    {
        Console.WriteLine("Entering method: {0}.{1}", args.Method.DeclaringType.Name, args.Method.Name);
    }

    public void OnException(MethodExecutionArgs args)
    {
    }

    public void OnExit(MethodExecutionArgs args)
    {
        Console.WriteLine("Exiting method: {0}.{1}", args.Method.DeclaringType.Name, args.Method.Name);
    }

    public void OnSuccess(MethodExecutionArgs args)
    {
    }

    public void RuntimeInitialize(MethodBase method)
    {
    }

}

Our aspect is nothing special, just an aspect that implements IOnMethodBoundary and we are just adding code to the OnEntry and OnExit advices.

Add another class file and name it TraceAspectProvider.cs then add the following code:

public class TraceAspectProvider : IAspectProvider
{
    readonly SomeTracingAspect aspectToApply = new SomeTracingAspect();

public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
    {
        Assembly assembly = (Assembly)targetElement;

List<AspectInstance> instances = new List<AspectInstance>();
        foreach (Type type in assembly.GetTypes())
        {
            ProcessType(type, instances);
        }

        return instances;
    }

    private void ProcessType(Type type, List instances)
    {
        foreach (MethodInfo targetMethod in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
        {
            instances.Add(new AspectInstance(targetMethod, aspectToApply));
        }

        foreach (Type nestedType in type.GetNestedTypes())
        {
            ProcessType(nestedType, instances);
        }
    }
}
 

This is how we tell PostSharp what targets to work with and what aspects to apply to those targets. We implement the IAspectProvider interface and satisfy its required ProvideAspects method. PostSharp invokes the ProvideAspects method passing in a System.Reflection.Assembly instance for the target assembly we’re trying to modify. Using reflection, we’ll get all of the types in the target assembly and process each one by calling our ProcessType method.

In the ProcessType method, we again use reflection to discover all of the methods in the given type that are public and not static. For each applicable method we create a new AspectInstance and add it to the instances list that we’ll be giving back to PostSharp for processing. Then we do some recursion to process any nested types.

When we create a new AspectInstance, we’re passing in to the first parameter the System.Reflection.MethodInfo instance we received from GetMethods(). This parameter can take System.Type, most of the System.Reflection types such as FieldInfo, EventInfo etc and also PostSharp.Reflection.LocationInfo. If we were attempting to apply a LocationInterception aspect, we would pass in a System.Reflection.PropertyInfo type for our intended target.

The second parameter requires an instance of IAspect that will be applied to the target. This is where we supply our desired aspect. We’ve declared and instantiated a class member of type SomeTracingAspect for this purpose so we supply this instance to satisfy the parameter.

Build the project and copy the MyProviders.dll and PostSharp.dll to the Lab folder.

Modify!

Now we’re all ready to go. The last step is to actually perform the transformations. To do this we just need to run the PostSharp command line interface with some specific options. I recommend creating a batch file to do this because it could be a lot of typing.

@"C:\Program Files (x86)\PostSharp 2.1\Release\postsharp.4.0-x64-cil.exe" "DummyApp.exe" /p:AspectProviders=MyProviders.TraceAspectProvider,MyProviders /p:Output=output\DummyApp.exe /attach
@copy /y MyProviders.dll .\Output
@copy /y PostSharp.dll .\Output
@pause

Your PostSharp path may vary. You will also need to choose the correct version of PostSharp CIL executable to run. If you’re working with assemblies targeting .NET 3.5 then use the 2.0 version. If you’re working with assemblies targeting .NET 4.0 then use the 4.0 version and of course, choose the appropriate architecture type (x86, x64). For example:

  • postsharp.2.0-x86-cil.exe
  • postsharp.2.0-x64-cil.exe
  • postsharp.4.0-x86-cil.exe
  • postsharp.4.0-x64-cil.exe

The first argument we pass in is the path to the target, DummyApp.exe. The target doesn’t have to be an executable, it can be a library (DLL).

Next we tell PostSharp which aspect provider to use. We do this using the /P argument which allows us to set PostSharp properties (Usage: /P:name=value). Of course we want to use the aspect provider we just built so we specify the fully qualified type name followed by a comma then the assembly name.

PostSharp requires an Output to be provided so we use the /P argument again this time setting the ‘Output’ property to our destination. We don’t want to modify the source so we tell PostSharp to put the result in the output folder.

Optionally we can specify the /attach argument which allows us to attach to Visual Studio to step through the provider (in case there are issues).

When we run the batch file, PostSharp goes to work. When it’s done we see that the output folder has a copy of DummyApp.exe. When we run it we see that our aspect has been applied.

Entering method: ExampleClass.Method1
Method1()
Exiting method: ExampleClass.Method1
Entering method: ExampleClass.Method2
Method2()
Exiting method: ExampleClass.Method2

We can also use ILSpy to look at the resulting assembly.

DummyApp-ILSpy

In another post we’ll go over applying an EventInterceptionAspect based aspect and how to setup project build options to automatically run this process.

kick it on DotNetKicks.com

Advertisements