Calendar

<<  mars 2010  >>
lumamejevesadi
22232425262728
1234567
891011121314
15161718192021
22232425262728
2930311234

View posts in large calendar
Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2010

(novembre 19, 2009 08:56)

Introduction

In many scenario, you will want to generate fields in an existing class of an existing assembly. Let's imagine for example that you want to do some lazy loading in your classes. As a consequence, in all of your properties you will have some code checking if your private backing field has already been loaded or not. For that, you will have two solutions : look the nullity of your backing field, or look a boolean variable "hasBeenLoaded". This second solution is the best. Indeed the nullity can be a valid value and retreiving a "null" from your database shall prevent you from doing another get in your database.

Of course, this solution will result in a lots of cumbersome code that you will need to propagate in all of your classes. So you will probably think "aspect" and so use an AOP framework like PostSharp to do all of this plumbing code. You will find many posts on Internet speaking of dealing with LazyLoading functionality in PostSharp, but they usually deal with the nullity of the backing field.

So how can we use PostSharp (I will use the 1.5 version for this example. The version 2.0 of PostSharp will probably simplify this solution) to generate a boolean field ?

This example (that should be seen more like a proof of concept) will probably make you think of many other cases where you will need to generate some code in an existing class. Here we will generate a simple variable in a class, but the code can be easily adapted to generate a field per existing property.

Let's create an aspect

With PostSharp, many high-level aspects already exist and they will be sufficient for many of your scenarios. However for code generation, you will need to go a bit more low level.

So in your project you will need to add a reference to PostSharp.Public (in the GAC) and to PostSharp.Core (that can be found in the installation folder of PostSharp). Now you can create your attribute :

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]

[MulticastAttributeUsage(MulticastTargets.Class, AllowMultiple = false)]

[RequirePostSharp("InjectCodeAttributePlugin", "InjectCodeAttributeTask")]

public class InjectCodeAttribute : MulticastAttribute { }

With the AttributeUsage attribute, I specify that this attribute will target only classes, and the MulticastAttributeUsage attribute tells that only the tagged class will be "aspected" (I could here decide that when I put my attribute on a class, it will get automatically propagated to all properties). I also specify the RequirePostSharp attribute. Indeed as we work low-level, we need to tell PostSharp what to do when seeing this attribute. So in this atribute, we specify the plugin file and the task related to our attribute.

The plugin file can be seen like a configuration file, listing tasks to perform and dependencies between them. Let's see the file we will create in our case. In our attribute, we have specified InjectCodeAttributePlugin, so it means that PostSharp will look for a file named InjectCodeAttributePlugin.psplugin. Let's create such a file in our project:

<?xml version="1.0" encoding="utf-8" ?>

<PlugIn xmlns="http://schemas.postsharp.org/1.0/configuration">

   <SearchPath Directory="bin/{$Configuration}"/>

   <TaskType Name="InjectCodeAttributeTask" 

             Implementation="LowLevelAspect.InjectCodeAttributeTask, LowLevelAspect">

      <Dependency TaskType="CodeWeaver"/>

   </TaskType>

</PlugIn>

What does it say ? Simply that the task InjectCodeAttributeTask (the name we specified in the RequirePostSharp attribute) will be implemented by a class of the same name, in a specific assembly and namespace. We also specify that our task will depend on a task called CodeWeaver. This is an out-of-the-box task of PostSharp that allow us to re-use the current weaver process. It's a best practice for nearly all the low level tasks you will create.

Let's create a task

So what does the task ? In my view, it can be seen as an orchestrator (or provider) of advices. And an advice is the object that will inject (weave) code in our class. Remember that we are doing low level aspects. So when we speak of code, we mean IL code.

Our task is an IAdviceProvider, meaning that it will provide PostSharp with the different advices we want to define (only one in our case : the one responsible for generating a new field). So the task's responsability is limited to two things:

  • As the task knows on which specific member it's weaving (type, method, ...), it can extract everything our advices will need for injection (types, methods, ...)
  • Iterate thru all the attributes (and attribute's targets) we have specified in our code to create the advices. Here is how we deal with that :

internal class InjectCodeAttributeTask : Task, IAdviceProvider

{

   /// <summary>

   /// Initialize the task.

   /// This is where we should extract some types or methods our advices will use

   /// </summary>

   protected override void Initialize()

   {

      base.Initialize();

   }

 

   void IAdviceProvider.ProvideAdvices(Weaver codeWeaver)

   {

      // Gets the dictionary of all custom attributes and more specifically, extract our attribute

      var annotationRepository = AnnotationRepositoryTask.GetTask(this.Project);

      var customAttributeEnumerator

         = annotationRepository.GetAnnotationsOfType(typeof(InjectCodeAttribute), false);

 

      // For each instance of our InjectCodeAttribute

      while ( customAttributeEnumerator.MoveNext() )

      {

         // Gets a reference to the attribute and then the type to which it applies

         // (Remember the MulticastAttributeUsage targets types !)

         InjectCodeAttribute attribute

            = (InjectCodeAttribute)

               CustomAttributeHelper.ConstructRuntimeObject(customAttributeEnumerator.Current.Value,

                                                            this.Project.Module);

         var type = (TypeDefDeclaration)customAttributeEnumerator.Current.TargetElement;

 

         // And we can add our advices

         codeWeaver.AddTypeLevelAdvice(new AddFieldAdvice(this, attribute, type),

                                       JoinPointKinds.AfterInstanceInitialization,

                                       new Singleton<TypeDefDeclaration>(type));

      }

   }

Note that we will complete later on the Initialize method. The JoinPointKinds enum of the AddTypeLevelAdvice is useless in our case. Its goal is to tell PostSharp where we want to inject code in the methods or classes. In our case creating our fields is independant of the existing code so we can be agnostic of the present code and so we just choose one of type-level JoinPointKinds.

So what do we want to generate ? We said a boolean field that can logically be decorated with an attribute CompilerGenerated. This is typically the kind of information we need for the Initialize method. Let's extract these informations.

   internal ITypeSignature SystemBoolean;

   internal IMethod CompilerGeneratedAttributeCtor;

 

   /// <summary>

   /// Initialize the task.

   /// This is where we should extract some types or methods our advices will use

   /// </summary>

   protected override void Initialize()

   {

      base.Initialize();

 

      var module = this.Project.Module;

      //1. Extract the System.Boolean type

      this.SystemBoolean = module.FindType(typeof(bool), BindingOptions.Default);

 

      //2. Extract the constructor (ie the method named ".ctor") of the CompilerGeneratedAttribute

      var compilerGeneratedAttribute = module.FindType(typeof(CompilerGeneratedAttribute), BindingOptions.Default);

      this.CompilerGeneratedAttributeCtor = module.FindMethod(compilerGeneratedAttribute, ".ctor");

   }

Note that for an attribute, we are not interested by the type itself, but by its constructor.

Let's create our advice

So let's go to our advice ! This is the class that will generate the field. This is the easiest part of our job !

internal class AddFieldAdvice : IAdvice

{

   private InjectCodeAttributeTask task;

   private InjectCodeAttribute attribute;

   private TypeDefDeclaration type;

 

   public AddFieldAdvice(InjectCodeAttributeTask task, InjectCodeAttribute attribute, TypeDefDeclaration type)

   {

      this.task = task;

      this.attribute = attribute;

      this.type = type;

   }

 

   int IAdvice.Priority

   {

      get { return this.attribute.AttributePriority; }

   }

 

   bool IAdvice.RequiresWeave(WeavingContext context)

   {

      return true;

   }

 

   void IAdvice.Weave(WeavingContext context, InstructionBlock block)

   {

      //1. Create a new field

      FieldDefDeclaration field = new FieldDefDeclaration();

      field.FieldType = this.task.SystemBoolean;

      field.Name = "HasBeenLoaded";

      field.Attributes = FieldAttributes.Public;

 

      //2. Add the field to the class' fields

      type.Fields.Add(field);

 

      //3. Add the attributes

      //(the field must be added to the class' fields)

      var ctor = this.task.CompilerGeneratedAttributeCtor;

      var compilerGenerated = new CustomAttributeDeclaration(ctor);

      field.CustomAttributes.Add(compilerGenerated);

   }

}

Note that we must add the field to the class' fields before we can add the attribute to our field. Otherwise we would get an exception telling that the field's parent is not known.

Ready to use our attribute !

Our attribute is now finished and we can test it !

So I just create a separate project (our attribute will be typically created in a framework DLL and not in your business DLL) referencing the DLL where my attribute reside and I add a basic class like this one :

[InjectCode]

public class MyAspectedClass { }

and I build it. But it does not work ! I have the following compilation error : Error 2 PostSharp: The plug-in "InjectCodeAttributePlugin" required by the type "LowLevelAspect.InjectCodeAttribute" was not found.
What happen is simple. PostSharp is searching for the file ".psplugin" but it cannot find it. So what to do ? You must know that by default PostSharp knows about the plugin files that are deployed on the machine under the "Plugin" folder (typically in C:\Program Files\PostSharp 1.5\PlugIns). But you can also give him a hint of where to find it. To do so, just edit your project file to add this property :

<PropertyGroup>

   <PostSharpSearchPath>..\LowLevelAspect\bin\Debug</PostSharpSearchPath>

</PropertyGroup>

In my case, LowLevelAspect is the name of the project where I defined my aspect. Note that you could also replace "Debug" with $(Configuration) to target either the Debug or the Release folder depending of the configuration you are building in

For this to work, you need of course to deploy your psplugin file. So you can just update the properties of your file to specify Copy to Ouput Directory : Copy Always.

So let's try again to compile. This time, it's ok !

We can now look to our class in the reflector and here what we get :

public class MyAspectedClass

{

   // Fields

   [CompilerGenerated]

   public bool HasBeenLoaded;

 

   // Methods

   public MyAspectedClass();

}

Pretty cool no ?

(octobre 20, 2009 22:08)

A few years ago, when I discovered AOP, Didier Danse told me about an AOP framework he was liking for .NET : PostSharp by Gaël Fraiteur. So I started to try it, and simply felt in love with this tool.

I do not want here to explain how to use it (I will try to post some examples about that in the future) but for now, I just want to tell you my story about PostSharp, and why I really started to use it.

The context

A few years ago, I was writing data oriented tests : typically, I was inserted some data, checking the behavior of my web app and then cleaning my data. And so my tests started to look like this :

[TestMethod]

public void Mytest()

{

   try

   {

      InsertSomeData();

      InsertSomeOtherData();

 

      DoMyTest();

   }

   finally

   {

      CleanMyOtherData();

      CleanMyData();

   }

}

It was working, but was a bit too verbose and my test code was "lost among noise".

Kind of AOP using the .NET framework

I was not knowing AOP at that time but I was sure I was able to do something different. I read the book of Jason Bock called "Applied .NET Attributes" and I discovered the ContextBoundObject and I turn my code to this :

[TestMethod]

[InsertSomeData]

[InsertSomeOtherData]

public void Mytest()

{

   DoMyTest();

}

Much better and elegant. My attributes were doing the stuff of inserting and deleting and there were no more noise in my test.
But dealing with messages and sinks is not so simple and has a lot of limitation. Above all, all this stuff is done at runtime and impose you to inherit from ContextBoundObject. Possible for my tests, but no further application for my production code.

The revelation

It's at this moment that one of my colleague has done a thesis about AOP and that I met Didier Danse. He told me about PostSharp and I started to test it. And quickly I had migrated all my attributes into PostSharp. And of course I had in the meanwhile extended their capabilities for keeping my test very declarative:

[TestMethod]

[InsertSomeData]

[InsertSomeOtherData]

[ExecuteThisTestSeveralTimeAs("user1", "user2", "user3")]

[IgnoreThisTestIfDbVersionIsInferiorTo(35)]

public void Mytest()

{

   DoMyTest();

}

I was no longer injecting (asking PostSharp to inject) code into my test, but also modifying its metadata attributes ! I was indeed adding some IgnoreAttribute to my code at postcompilation if my test was not relevant to my current test context. This was alllowing me not only to reduce the number of test I had to write, but also to keep a very simple test result file without any inconclusive test.

My present

And I started to see so many possible applications in my code - without any performance penalty as every aspet I was thinking to would be woven at compilation time. (Well indeed there is a small performance penalty as PostSharp inject some additional method call but this is practically negligeable in practice. Note moreover that PostSharp 2.0 will just remove this extra overhead !)

My next step was when I wanted to do some performance investigation in my code. Usually, I add some Stopwatches in my code to capture the execution time and log that to a file. But in my case, I wanted to instrument many many methods that were calling each other. Ok... Let's do an assembly attribute and work with "OnMethodInvocationAspect" and using some filtering on my class names and method names !
In a few lines of code, I had instrumented thousands of methods (but only the one I wanted to). Pretty awsome.


What about now ?
Postsharp is now a key feature of my designs. How could I make a clean INotifyPropertyChanged without it ?

I would not say PostSharp is transparent to use. There is of course a learning cost and a vocabulary, specially if you do not know AOP by itself. But this learning cost is quite small compared to the productivity you can obtain in your development.
In my view, it's the best way to create some cross-cutting functionality in a very elegant and efficient way ! And when you want to enforce some design rules in your code, working declaratively is a dream ! Specially when you can work at interface level !

(août 20, 2007 08:45)
A few days ago, Loïc Bar (http://www.loicbar.com/) has posted a new feed to explain how to customize an ASP.NET application using master pages (and how to store this value in the user profile).
As added in the feed comments, I wanted to react to go a bit further.
Indeed this is typically an AOP problem that may (should) be adressed with AOP. You can find here the original post of Loïc Bar.
And you can download here my article explaining how to achieve the same goal using HttpModules in ASP.NET. This article will be my reference one for when speaking - later on - of HttpModules, as it shows, step by step, how to create it and how to handle it.

Powered by BlogEngine.NET 1.2.0.0 | Theme by Pierre-Emmanuel Dautreppe