Other Posts in AOP

  1. Creating an AOP Library in .Net - Part 1
  2. Creating an AOP Library in .Net - Part 2
  3. Creating an AOP Library in .Net - Part 3
  4. Creating an AOP Library in .Net - Part 4

Creating an AOP Library in .Net - Part 4

7/20/2010
When I last left off, I had a basic Manager class that went through our aspects and allowed them to inject their own IL into the sub class at various join points. To be completely honest, doing it that way, while easy to set up, is annoying at best to anyone who wants to actually write an aspect. Instead I would rather write my aspect in C# or VB and not have to worry about IL at all. So I ended up taking another approach, interface injection.
 
By the way, before I go on, I should let you know that I tend to use various terms the way that I remember/think of things. So if you hear interface injection and think of Martin Fowler's version of that phrase, I'm not talking about that. Well not exactly anyway. When I say interface injection, I mean that at run time we add an interface to an already existing class. That's it. Has nothing really to do with IoC containers, etc. And actually since we're using sub classing, we're not adding it to the class directly but instead to the sub class and implementing it.
 
Basically I want each aspect, if they so choose, to be able to add their own interface/implementation to the sub class. Now this would require IL generation, but once implemented it could be accessed using normal code from anywhere, including other aspects. Using this technique, I had the manager implement one interface that contained events for when the function started, ended, or when an error occurred. Thus most aspects would simply be able to tie into these events in order to accomplish what they wanted and the interface injection would be a special case. So let's look at some code:
 
   1: public class Manager:Singleton<Manager>
   2: {
   3:     protected Manager()
   4:         : base()
   5:     {
   6:         Configuration = Gestalt.Manager.Instance.GetConfigFile<Configuration>("AspectusConfiguration");
   7:         if (Configuration.AspectLocation.EndsWith(".dll",StringComparison.CurrentCultureIgnoreCase))
   8:         {
   9:             Assembly TempAssembly = Assembly.LoadFile(Configuration.AspectLocation);
  10:             Gestalt.Manager.Instance.RegisterConfigFile(TempAssembly);
  11:             DotExtension.Manager.Instance.Setup(TempAssembly);
  12:         }
  13:         else if (Utilities.IO.FileManager.DirectoryExists(Configuration.AspectLocation))
  14:         {
  15:             List<Assembly> Assemblies = Utilities.Reflection.Reflection.GetAssembliesFromDirectory(Configuration.AspectLocation, true);
  16:             foreach (Assembly Assembly in Assemblies)
  17:             {
  18:                 Gestalt.Manager.Instance.RegisterConfigFile(Assembly);
  19:                 DotExtension.Manager.Instance.Setup(Assembly);
  20:             }
  21:         }
  22:         else
  23:         {
  24:             Assembly TempAssembly = Assembly.Load(Configuration.AspectLocation);
  25:             Gestalt.Manager.Instance.RegisterConfigFile(TempAssembly);
  26:             DotExtension.Manager.Instance.Setup(TempAssembly);
  27:         }
  28:         Classes = new Dictionary<Type, Type>();
  29:         AssemblyName Name = new AssemblyName(Configuration.AssemblyName);
  30:         AppDomain Domain = Thread.GetDomain();
  31:         Builder = Domain.DefineDynamicAssembly(Name, AssemblyBuilderAccess.RunAndSave, Configuration.AssemblyDirectory);
  32:         Module = Builder.DefineDynamicModule(Configuration.AssemblyName, Configuration.AssemblyName + ".dll", true);
  33:     }
  34:  
  35:     public void Save()
  36:     {
  37:         Builder.Save(Configuration.AssemblyName + ".dll");
  38:     }
  39:  
  40:     public void Setup(Type Type)
  41:     {
  42:         List<Type> Interfaces = new List<Type>();
  43:         foreach (IAspect Extension in DotExtension.Manager.Instance.Extensions.OfType<IAspect>())
  44:         {
  45:             if (Extension.InterfacesUsing != null)
  46:             {
  47:                 Interfaces.AddRange(Extension.InterfacesUsing);
  48:             }
  49:         }
  50:         Interfaces.Add(typeof(IEvents));
  51:         TypeBuilder TypeBuilder = Module.DefineType(Configuration.AssemblyName + "." + Type.Name + "Derived", TypeAttributes.Public, Type, Interfaces.ToArray());
  52:         MethodAttributes GetSetAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Virtual;
  53:         CreateStartingEvent(TypeBuilder, GetSetAttributes);
  54:         CreateEndingEvent(TypeBuilder, GetSetAttributes);
  55:         CreateExceptionEvent(TypeBuilder, GetSetAttributes);
  56:  
  57:         foreach (IAspect Extension in DotExtension.Manager.Instance.Extensions.OfType<IAspect>())
  58:         {
  59:             Extension.SetupInterfaces(TypeBuilder);
  60:         }
  61:  
  62:         foreach (MethodInfo Method in Type.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance))
  63:         {
  64:             if (Method.IsVirtual)
  65:             {
  66:                 CreateMethod(Type, Method, TypeBuilder);
  67:             }
  68:         }
  69:  
  70:         Classes.Add(Type, TypeBuilder.CreateType());
  71:     }
  72:  
  73:     public T Create<T>()
  74:     {
  75:         T ReturnObject = (T)Classes[typeof(T)].Assembly.CreateInstance(Classes[typeof(T)].FullName);
  76:         foreach (IAspect Extension in DotExtension.Manager.Instance.Extensions.OfType<IAspect>())
  77:         {
  78:             Extension.Setup(ReturnObject);
  79:         }
  80:         return ReturnObject;
  81:     }
  82:  
  83:     private void CreateMethod(Type Type, MethodInfo Method, TypeBuilder TypeBuilder)
  84:     {
  85:         MethodAttributes MethodAttribute = MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Public;
  86:         if (Method.IsStatic)
  87:             MethodAttribute |= MethodAttributes.Static;
  88:         ParameterInfo[] Parameters = Method.GetParameters();
  89:         Type[] ParameterTypes = new Type[Parameters.Length];
  90:         int x = 0;
  91:         foreach (ParameterInfo Parameter in Parameters)
  92:         {
  93:             ParameterTypes[x] = Parameter.ParameterType;
  94:             ++x;
  95:         }
  96:         MethodBuilder TempMethodBuilder = TypeBuilder.DefineMethod(Method.Name, MethodAttribute, Method.ReturnType, ParameterTypes);
  97:         ILGenerator MethodILGenerator = TempMethodBuilder.GetILGenerator();
  98:         LocalBuilder LocalReturn = null;
  99:         if (Method.ReturnType != typeof(void))
 100:         {
 101:             LocalReturn = MethodILGenerator.DeclareLocal(Method.ReturnType);
 102:         }
 103:         LocalBuilder StartingArgs = MethodILGenerator.DeclareLocal(typeof(Starting));
 104:         LocalBuilder EndingArgs = MethodILGenerator.DeclareLocal(typeof(Ending));
 105:         LocalBuilder ErrorArgs = MethodILGenerator.DeclareLocal(typeof(Aspectus.EventArgs.Exception));
 106:         Label MethodTryEndLabel = MethodILGenerator.DefineLabel();
 107:         MethodILGenerator.BeginExceptionBlock();
 108:         AddStartingBlock(MethodILGenerator, StartingArgs, Method, LocalReturn);
 109:         foreach (IAspect Extension in DotExtension.Manager.Instance.Extensions.OfType<IAspect>())
 110:         {
 111:             Extension.SetupStartMethod(MethodILGenerator, Method, Type);
 112:         }
 113:         if (!Method.IsStatic)
 114:         {
 115:             MethodILGenerator.Emit(OpCodes.Ldarg_0);
 116:         }
 117:         foreach (ParameterInfo Parameter in Parameters)
 118:         {
 119:             MethodILGenerator.Emit(OpCodes.Ldarg, Parameter.Position + (Method.IsStatic ? 0 : 1));
 120:         }
 121:         MethodILGenerator.EmitCall(OpCodes.Call, Method, new Type[0]);
 122:         if (Method.ReturnType != typeof(void))
 123:         {
 124:             MethodILGenerator.Emit(OpCodes.Stloc, LocalReturn.LocalIndex);
 125:         }
 126:         MethodILGenerator.Emit(OpCodes.Br_S, MethodTryEndLabel);
 127:         AddExceptionBlock(MethodILGenerator, Method, Type, ErrorArgs);
 128:  
 129:         MethodILGenerator.MarkLabel(MethodTryEndLabel);
 130:  
 131:         foreach (IAspect Extension in DotExtension.Manager.Instance.Extensions.OfType<IAspect>())
 132:         {
 133:             Extension.SetupEndMethod(MethodILGenerator, Method, Type, LocalReturn);
 134:         }
 135:  
 136:         AddEndingBlock(MethodILGenerator, EndingArgs, Method, LocalReturn);
 137:         if (Method.ReturnType != typeof(void))
 138:         {
 139:             MethodILGenerator.Emit(OpCodes.Ldloc, LocalReturn.LocalIndex);
 140:         }
 141:         MethodILGenerator.Emit(OpCodes.Ret);
 142:         TypeBuilder.DefineMethodOverride(TempMethodBuilder, Method);
 143:     }
 144:  
 145:     private void AddEndingBlock(ILGenerator Generator, LocalBuilder EndingArgs, MethodInfo Method,LocalBuilder LocalReturn)
 146:     {
 147:         Generator.Emit(OpCodes.Newobj, typeof(Ending).GetConstructor(new Type[0]));
 148:         Generator.Emit(OpCodes.Stloc, EndingArgs.LocalIndex);
 149:         Generator.Emit(OpCodes.Ldloc, EndingArgs.LocalIndex);
 150:         Generator.Emit(OpCodes.Ldstr, Method.Name);
 151:         Generator.EmitCall(OpCodes.Callvirt, typeof(Ending).GetProperty("MethodName").GetSetMethod(), null);
 152:         if (LocalReturn != null)
 153:         {
 154:             Generator.Emit(OpCodes.Ldloc, EndingArgs.LocalIndex);
 155:             Generator.Emit(OpCodes.Ldloc, LocalReturn.LocalIndex);
 156:             Generator.Emit(OpCodes.Box, LocalReturn.LocalType);
 157:             Generator.EmitCall(OpCodes.Callvirt, typeof(Ending).GetProperty("ReturnValue").GetSetMethod(), null);
 158:         }
 159:         ParameterInfo[] Parameters = Method.GetParameters();
 160:         foreach (ParameterInfo Parameter in Parameters)
 161:         {
 162:             Generator.Emit(OpCodes.Ldloc, EndingArgs.LocalIndex);
 163:             Generator.EmitCall(OpCodes.Callvirt, typeof(Ending).GetProperty("Parameters").GetGetMethod(), null);
 164:             Generator.Emit(OpCodes.Ldarg, Parameter.Position + (Method.IsStatic ? 0 : 1));
 165:             if (!Parameter.ParameterType.IsByRef)
 166:             {
 167:                 Generator.Emit(OpCodes.Box, Parameter.ParameterType);
 168:             }
 169:             else
 170:             {
 171:                 Generator.Emit(OpCodes.Box, typeof(int));
 172:             }
 173:             Generator.EmitCall(OpCodes.Callvirt, typeof(List<object>).GetMethod("Add"), null);
 174:         }
 175:         Generator.Emit(OpCodes.Nop);
 176:         Generator.Emit(OpCodes.Ldarg_0);
 177:         Generator.Emit(OpCodes.Castclass, typeof(IEvents));
 178:         Generator.EmitCall(OpCodes.Callvirt, typeof(IEvents).GetProperty("Aspectus_Ending").GetGetMethod(), null);
 179:         Generator.Emit(OpCodes.Ldarg_0);
 180:         Generator.Emit(OpCodes.Ldloc, EndingArgs.LocalIndex);
 181:         Type EventHelperType = typeof(Utilities.Events.EventHelper);
 182:         MethodInfo[] Methods = EventHelperType.GetMethods();
 183:         MethodInfo TempMethod = null;
 184:         foreach (MethodInfo TempMethodInfo in Methods)
 185:         {
 186:             if (TempMethodInfo.GetParameters().Length == 3)
 187:             {
 188:                 TempMethod = TempMethodInfo;
 189:                 break;
 190:             }
 191:         }
 192:         TempMethod = TempMethod.MakeGenericMethod(new Type[] { typeof(Ending) });
 193:         Generator.EmitCall(OpCodes.Call, TempMethod, null);
 194:         if (LocalReturn != null)
 195:         {
 196:             Label Equal = Generator.DefineLabel();
 197:             Generator.Emit(OpCodes.Ldloc, EndingArgs.LocalIndex);
 198:             Generator.EmitCall(OpCodes.Callvirt, typeof(Ending).GetProperty("ReturnValue").GetGetMethod(), null);
 199:             Generator.Emit(OpCodes.Ldnull);
 200:             Generator.Emit(OpCodes.Ceq);
 201:             Generator.Emit(OpCodes.Brtrue_S, Equal);
 202:             Generator.Emit(OpCodes.Ldloc, EndingArgs.LocalIndex);
 203:             Generator.EmitCall(OpCodes.Callvirt, typeof(Ending).GetProperty("ReturnValue").GetGetMethod(), null);
 204:             Generator.Emit(OpCodes.Unbox_Any, LocalReturn.LocalType);
 205:             Generator.Emit(OpCodes.Stloc, LocalReturn.LocalIndex);
 206:             Generator.MarkLabel(Equal);
 207:         }
 208:     }
 209:  
 210:     private void AddExceptionBlock(ILGenerator Generator, MethodInfo MethodInfo, Type Type, LocalBuilder ErrorArgs)
 211:     {
 212:         Generator.BeginCatchBlock(typeof(System.Exception));
 213:  
 214:         Generator.Emit(OpCodes.Stloc_0);
 215:         Generator.Emit(OpCodes.Newobj, typeof(Aspectus.EventArgs.Exception).GetConstructor(new Type[0]));
 216:         Generator.Emit(OpCodes.Stloc, ErrorArgs.LocalIndex);
 217:         Generator.Emit(OpCodes.Ldloc, ErrorArgs.LocalIndex);
 218:         Generator.Emit(OpCodes.Ldloc_0);
 219:         Generator.EmitCall(OpCodes.Callvirt, typeof(Aspectus.EventArgs.Exception).GetProperty("InternalException").GetSetMethod(), null);
 220:         Generator.Emit(OpCodes.Nop);
 221:         Generator.Emit(OpCodes.Ldarg_0);
 222:         Generator.Emit(OpCodes.Castclass, typeof(IEvents));
 223:         Generator.EmitCall(OpCodes.Callvirt, typeof(IEvents).GetProperty("Aspectus_Exception").GetGetMethod(), null);
 224:         Generator.Emit(OpCodes.Ldarg_0);
 225:         Generator.Emit(OpCodes.Ldloc, ErrorArgs.LocalIndex);
 226:         Type EventHelperType = typeof(Utilities.Events.EventHelper);
 227:         MethodInfo[] Methods = EventHelperType.GetMethods();
 228:         MethodInfo TempMethod = null;
 229:         foreach (MethodInfo Method in Methods)
 230:         {
 231:             if (Method.GetParameters().Length == 3)
 232:             {
 233:                 TempMethod = Method;
 234:                 break;
 235:             }
 236:         }
 237:         TempMethod = TempMethod.MakeGenericMethod(new Type[] { typeof(Aspectus.EventArgs.Exception) });
 238:         Generator.EmitCall(OpCodes.Call, TempMethod, null);
 239:         foreach (IAspect Extension in DotExtension.Manager.Instance.Extensions.OfType<IAspect>())
 240:         {
 241:             Extension.SetupExceptionMethod(Generator, MethodInfo, Type);
 242:         }
 243:         Generator.Emit(OpCodes.Rethrow);
 244:         Generator.EndExceptionBlock();
 245:     }
 246:  
 247:     private void AddStartingBlock(ILGenerator Generator, LocalBuilder StartingArgs, MethodInfo Method,LocalBuilder LocalReturn)
 248:     {
 249:         Generator.Emit(OpCodes.Newobj, typeof(Starting).GetConstructor(new Type[0]));
 250:         Generator.Emit(OpCodes.Stloc, StartingArgs.LocalIndex);
 251:         Generator.Emit(OpCodes.Ldloc, StartingArgs.LocalIndex);
 252:         Generator.Emit(OpCodes.Ldstr, Method.Name);
 253:         Generator.EmitCall(OpCodes.Callvirt, typeof(Starting).GetProperty("MethodName").GetSetMethod(), null);
 254:         ParameterInfo[] Parameters = Method.GetParameters();
 255:         foreach (ParameterInfo Parameter in Parameters)
 256:         {
 257:                 Generator.Emit(OpCodes.Ldloc, StartingArgs.LocalIndex);
 258:                 Generator.EmitCall(OpCodes.Callvirt, typeof(Starting).GetProperty("Parameters").GetGetMethod(), null);
 259:                 Generator.Emit(OpCodes.Ldarg, Parameter.Position + (Method.IsStatic ? 0 : 1));
 260:                 if (!Parameter.ParameterType.IsByRef)
 261:                 {
 262:                     Generator.Emit(OpCodes.Box, Parameter.ParameterType);
 263:                 }
 264:                 else
 265:                 {
 266:                     Generator.Emit(OpCodes.Box, typeof(int));
 267:                 }
 268:                 Generator.EmitCall(OpCodes.Callvirt, typeof(List<object>).GetMethod("Add"), null);
 269:         }
 270:         Generator.Emit(OpCodes.Ldarg_0);
 271:         Generator.Emit(OpCodes.Castclass, typeof(IEvents));
 272:         Generator.EmitCall(OpCodes.Callvirt, typeof(IEvents).GetProperty("Aspectus_Starting").GetGetMethod(), null);
 273:         Generator.Emit(OpCodes.Ldarg_0);
 274:         Generator.Emit(OpCodes.Ldloc, StartingArgs.LocalIndex);
 275:         Type EventHelperType = typeof(Utilities.Events.EventHelper);
 276:         MethodInfo[] Methods = EventHelperType.GetMethods();
 277:         MethodInfo TempMethod = null;
 278:         foreach (MethodInfo TempMethodInfo in Methods)
 279:         {
 280:             if (TempMethodInfo.GetParameters().Length == 3)
 281:             {
 282:                 TempMethod = TempMethodInfo;
 283:                 break;
 284:             }
 285:         }
 286:         TempMethod = TempMethod.MakeGenericMethod(new Type[] { typeof(Starting) });
 287:         Generator.EmitCall(OpCodes.Call, TempMethod, null);
 288:         if (LocalReturn != null)
 289:         {
 290:             Label Equal = Generator.DefineLabel();
 291:             Generator.Emit(OpCodes.Ldloc, StartingArgs.LocalIndex);
 292:             Generator.EmitCall(OpCodes.Callvirt, typeof(Starting).GetProperty("ReturnValue").GetGetMethod(), null);
 293:             Generator.Emit(OpCodes.Ldnull);
 294:             Generator.Emit(OpCodes.Ceq);
 295:             Generator.Emit(OpCodes.Brtrue_S, Equal);
 296:             Generator.Emit(OpCodes.Ldloc, StartingArgs.LocalIndex);
 297:             Generator.EmitCall(OpCodes.Callvirt, typeof(Starting).GetProperty("ReturnValue").GetGetMethod(), null);
 298:             Generator.Emit(OpCodes.Unbox_Any, LocalReturn.LocalType);
 299:             Generator.Emit(OpCodes.Stloc, LocalReturn.LocalIndex);
 300:             Generator.Emit(OpCodes.Ldloc, LocalReturn.LocalIndex);
 301:             Generator.Emit(OpCodes.Ret);
 302:             Generator.MarkLabel(Equal);
 303:         }
 304:     }
 305:  
 306:     private void CreateStartingEvent(TypeBuilder TypeBuilder, MethodAttributes GetSetAttributes)
 307:     {
 308:         FieldBuilder FieldBuilder = TypeBuilder.DefineField("_Aspectus_Starting", typeof(EventHandler<Starting>), FieldAttributes.Private);
 309:         PropertyBuilder PropertyBuilder = TypeBuilder.DefineProperty("Aspectus_Starting", PropertyAttributes.SpecialName,
 310:             typeof(EventHandler<Starting>), null);
 311:         MethodBuilder ValuePropertyGet = TypeBuilder.DefineMethod("get_Aspectus_Starting", GetSetAttributes,
 312:             typeof(EventHandler<Starting>), Type.EmptyTypes);
 313:         ILGenerator GetGenerator = ValuePropertyGet.GetILGenerator();
 314:         GetGenerator.Emit(OpCodes.Ldarg_0);
 315:         GetGenerator.Emit(OpCodes.Ldfld, FieldBuilder);
 316:         GetGenerator.Emit(OpCodes.Ret);
 317:         PropertyBuilder.SetGetMethod(ValuePropertyGet);
 318:  
 319:         MethodBuilder ValuePropertySet = TypeBuilder.DefineMethod("set_Aspectus_Starting", GetSetAttributes, null,
 320:             new Type[] { typeof(EventHandler<Starting>) });
 321:         ILGenerator SetGenerator = ValuePropertySet.GetILGenerator();
 322:         SetGenerator.Emit(OpCodes.Ldarg_0);
 323:         SetGenerator.Emit(OpCodes.Ldarg_1);
 324:         SetGenerator.Emit(OpCodes.Stfld, FieldBuilder);
 325:         SetGenerator.Emit(OpCodes.Ret);
 326:         PropertyBuilder.SetSetMethod(ValuePropertySet);
 327:     }
 328:  
 329:     private void CreateEndingEvent(TypeBuilder TypeBuilder, MethodAttributes GetSetAttributes)
 330:     {
 331:         FieldBuilder FieldBuilder = TypeBuilder.DefineField("_Aspectus_Ending", typeof(EventHandler<Ending>), FieldAttributes.Private);
 332:         PropertyBuilder PropertyBuilder = TypeBuilder.DefineProperty("Aspectus_Ending", PropertyAttributes.SpecialName,
 333:             typeof(EventHandler<Ending>), null);
 334:         MethodBuilder ValuePropertyGet = TypeBuilder.DefineMethod("get_Aspectus_Ending", GetSetAttributes,
 335:             typeof(EventHandler<Ending>), Type.EmptyTypes);
 336:         ILGenerator GetGenerator = ValuePropertyGet.GetILGenerator();
 337:         GetGenerator.Emit(OpCodes.Ldarg_0);
 338:         GetGenerator.Emit(OpCodes.Ldfld, FieldBuilder);
 339:         GetGenerator.Emit(OpCodes.Ret);
 340:         PropertyBuilder.SetGetMethod(ValuePropertyGet);
 341:  
 342:         MethodBuilder ValuePropertySet = TypeBuilder.DefineMethod("set_Aspectus_Ending", GetSetAttributes, null,
 343:             new Type[] { typeof(EventHandler<Ending>) });
 344:         ILGenerator SetGenerator = ValuePropertySet.GetILGenerator();
 345:         SetGenerator.Emit(OpCodes.Ldarg_0);
 346:         SetGenerator.Emit(OpCodes.Ldarg_1);
 347:         SetGenerator.Emit(OpCodes.Stfld, FieldBuilder);
 348:         SetGenerator.Emit(OpCodes.Ret);
 349:         PropertyBuilder.SetSetMethod(ValuePropertySet);
 350:     }
 351:  
 352:     private void CreateExceptionEvent(TypeBuilder TypeBuilder, MethodAttributes GetSetAttributes)
 353:     {
 354:         FieldBuilder FieldBuilder = TypeBuilder.DefineField("_Aspectus_Exception", typeof(EventHandler<Aspectus.EventArgs.Exception>), FieldAttributes.Private);
 355:         PropertyBuilder PropertyBuilder = TypeBuilder.DefineProperty("Aspectus_Exception", PropertyAttributes.SpecialName,
 356:             typeof(EventHandler<Aspectus.EventArgs.Exception>), null);
 357:         MethodBuilder ValuePropertyGet = TypeBuilder.DefineMethod("get_Aspectus_Exception", GetSetAttributes,
 358:             typeof(EventHandler<Aspectus.EventArgs.Exception>), Type.EmptyTypes);
 359:         ILGenerator GetGenerator = ValuePropertyGet.GetILGenerator();
 360:         GetGenerator.Emit(OpCodes.Ldarg_0);
 361:         GetGenerator.Emit(OpCodes.Ldfld, FieldBuilder);
 362:         GetGenerator.Emit(OpCodes.Ret);
 363:         PropertyBuilder.SetGetMethod(ValuePropertyGet);
 364:  
 365:         MethodBuilder ValuePropertySet = TypeBuilder.DefineMethod("set_Aspectus_Exception", GetSetAttributes, null,
 366:             new Type[] { typeof(EventHandler<Aspectus.EventArgs.Exception>) });
 367:         ILGenerator SetGenerator = ValuePropertySet.GetILGenerator();
 368:         SetGenerator.Emit(OpCodes.Ldarg_0);
 369:         SetGenerator.Emit(OpCodes.Ldarg_1);
 370:         SetGenerator.Emit(OpCodes.Stfld, FieldBuilder);
 371:         SetGenerator.Emit(OpCodes.Ret);
 372:         PropertyBuilder.SetSetMethod(ValuePropertySet);
 373:     }
 374:  
 375:     protected AssemblyBuilder Builder { get; set; }
 376:     protected ModuleBuilder Module { get; set; }
 377:     protected Dictionary<Type, Type> Classes { get; set; }
 378:     protected Configuration Configuration { get; set; }
 379: }
 
Now the code above is a bit long, but it's the finished item (well finished as of this writing). So let's go over each function. Starting with the constructor, we start by loading the configuration object using Gestalt.Net. The if statement isn't that important, just checking if it's a DLL, directory, or preloaded DLL. We create a dictionary for mapping our classes to the sub classes, and set up our module. After that is simply the Save function. We've seen most of this before, but the following function, Setup, is where things start to change.
 
In Setup we start by getting extensions/aspects from DotExtension (I said at the beginning that I created all of those libraries for a reason). You'll notice that it asks for any extension that implements IAspect. IAspect is our aspect's interface:
   1: public interface IAspect
   2: {
   3:     void SetupStartMethod(ILGenerator MethodILGenerator, MethodInfo Method, Type Type);
   4:     void SetupEndMethod(ILGenerator MethodILGenerator, MethodInfo Method, Type Type, LocalBuilder ReturnValue);
   5:     void SetupExceptionMethod(ILGenerator MethodILGenerator, MethodInfo Method, Type Type);
   6:     void Setup(object Object);
   7:     void SetupInterfaces(TypeBuilder TypeBuilder);
   8:     List<Type> InterfacesUsing { get; set; }
   9: }
The interface contains a couple functions and one property. At this point we're interested in the property. Any interfaces that the aspect wishes to implement it adds now. It also adds one interface called IEvents:
   1: public interface IEvents
   2: {
   3:     #region Events
   4:  
   5:     /// <summary>
   6:     /// Called when property/function is ending
   7:     /// </summary>
   8:     EventHandler<Ending> Aspectus_Ending { get; set; }
   9:  
  10:     /// <summary>
  11:     /// Called when property/function is starting
  12:     /// </summary>
  13:     EventHandler<Starting> Aspectus_Starting { get; set; }
  14:  
  15:     /// <summary>
  16:     /// Called when an error is caught
  17:     /// </summary>
  18:     EventHandler<Exception> Aspectus_Exception { get; set; }
  19:  
  20:     #endregion
  21: }
IEvents is used by the manager to let aspects interact with the sub class. After that, it creates our sub class's builder, sending in the interfaces and telling it that we will be implementing them. The next bit are three new functions, CreateStartingEvent, CreateEndingEvent, and CreateExceptionEvent. You'll notice that what these three functions do is create an event field for each point and then the get/set functions for the property. Basically we're implementing IEvents.
 
Once we have IEvents implemented, we let the aspects implement their own interfaces by calling SetupInterfaces on each aspect. Finally we go back to rather familiar code, we get our list of methods and if they're virtual, we override them by calling CreateMethod. Once that is done we create the sub class and add it to our dictionary.
 
The CreateMethod function is where we start getting a bit more complex. We start by getting a list of parameter types, create our method that will override the preexisting method, setup a return value if we need it, etc. Mostly pretty basic stuff that we did last time. However after that we create three locals, StartingArgs, EndingArgs, and ErrorArgs. These are the actual EventArgs that we will be feeding the three events that we defined earlier. I guess now would be a good time to actually see what they contain:
   1: public class Starting : System.EventArgs
   2: {
   3:     public Starting()
   4:         : base()
   5:     {
   6:         MethodName = "";
   7:         Parameters = new List<object>();
   8:         ReturnValue = null;
   9:     }
  10:  
  11:     public string MethodName { get; set; }
  12:     public List<object> Parameters { get; set; }
  13:     public object ReturnValue { get; set; }
  14: }
  15:  
  16: public class Ending : System.EventArgs
  17: {
  18:     public Ending()
  19:         : base()
  20:     {
  21:         MethodName = "";
  22:         Parameters = new List<object>();
  23:         ReturnValue = null;
  24:     }
  25:  
  26:     public string MethodName { get; set; }
  27:     public List<object> Parameters { get; set; }
  28:     public object ReturnValue { get; set; }
  29: }
  30:  
  31: public class Exception:System.EventArgs
  32: {
  33:     public System.Exception InternalException { get; set; }
  34: }
You'll notice the Starting and Ending classes contain properties for the method name, a list of parameters, and the return value. These will be used to not only feed it information but also get information from the aspects. Anyway, back to CreateMethod... After the locals are defined we setup a label that is set at the end of our try/catch and start the try/catch block. The next function is AddStartingBlock. This function generates a bit of IL that initiates the StartingArgs, loads up the method name into MethodName, loads the parameters into the list, and then feeds StartingArgs to the Aspectus_Starting event. You may wish to note that loading the parameter list has two cases depending on whether or not it was passed in by reference or not. If it's by reference, we simply send it the pointer to the parameter. Otherwise we send the actual value. After the event is thrown, we check to see if there was a return value that was set by one of our aspects. If it was we stop our method and return that value. Otherwise we go back to CreateMethod.
 
Once back in CreateMethod, we allow the aspects to inject any IL that they need to at the start of the function. Note that this is most likely going to be for implementing the interface. We then call our base class's function, store the results in our local return value, and add our exception block. This is similar to the starting block but instead starts the catch portion of the try/catch and calls the exception event, and ultimately rethrows our exception. However if no exception occurs, we start our ending block. In this part we once again call our ending event, this time sending in our current return value along with the parameters and method name. We then check if they sent back a new value, etc. And if they did we return that instead of the original value. After all of that is done we set our new method to override our old method and we're done.
 
Past that we have our Create function, which is similar to our previous versions of it, except this time we allow each aspect to do any setup that they require (tie into the events, etc.). The only other bit of code that's of importance is a base class that I've yet to show you:
   1: public class Aspect : Extension, IAspect
   2: {
   3:     public virtual void Setup(object Object)
   4:     {
   5:     }
   6:  
   7:     public virtual List<Type> InterfacesUsing { get; set; }
   8:  
   9:     public virtual void SetupStartMethod(System.Reflection.Emit.ILGenerator MethodILGenerator, System.Reflection.MethodInfo Method, Type Type)
  10:     {
  11:  
  12:     }
  13:  
  14:  
  15:     public virtual void SetupEndMethod(System.Reflection.Emit.ILGenerator MethodILGenerator, System.Reflection.MethodInfo Method, Type Type, System.Reflection.Emit.LocalBuilder ReturnValue)
  16:     {
  17:     }
  18:  
  19:     public virtual void SetupExceptionMethod(System.Reflection.Emit.ILGenerator MethodILGenerator, System.Reflection.MethodInfo Method, Type Type)
  20:     {
  21:     }
  22:  
  23:     public virtual void SetupInterfaces(System.Reflection.Emit.TypeBuilder TypeBuilder)
  24:     {
  25:     }
  26: }
And all this does is implements Extension from DotExtension and IAspect so any aspect that is created can use this instead of remembering that it needs both of those items. With this basic setup I can easily implement aspects to do a number of things. For instance logging:
   1: public class LoggingAspect : Aspect
   2: {
   3:     public override void Setup(object Object)
   4:     {
   5:         try
   6:         {
   7:             ((IEvents)Object).Aspectus_Starting += Start;
   8:             ((IEvents)Object).Aspectus_Ending += End;
   9:             ((IEvents)Object).Aspectus_Exception += Exception;
  10:         }
  11:         catch { throw; }
  12:     }
  13:  
  14:     public void Start(object sender, Starting e)
  15:     {
  16:         try
  17:         {
  18:             StringBuilder Builder = new StringBuilder();
  19:             string Splitter = "";
  20:             foreach (object Parameter in e.Parameters)
  21:             {
  22:                 Builder.Append(Splitter + Parameter);
  23:                 Splitter = ",";
  24:             }
  25:             BlammoNet.Manager.Instance.GetLog().LogMessage("Entered method {0}({1})", MessageType.Debug, e.MethodName, Builder.ToString());
  26:         }
  27:         catch { throw; }
  28:     }
  29:  
  30:     public void End(object sender, Ending e)
  31:     {
  32:         try
  33:         {
  34:             StringBuilder Builder = new StringBuilder();
  35:             if (e.ReturnValue != null)
  36:             {
  37:                 Builder.Append(e.ReturnValue.ToString());
  38:             }
  39:             BlammoNet.Manager.Instance.GetLog().LogMessage("Exited method {0}: Return Value: {1}", MessageType.Debug, e.MethodName, Builder.ToString());
  40:         }
  41:         catch { throw; }
  42:     }
  43:  
  44:     public void Exception(object sender, Aspectus.EventArgs.Exception e)
  45:     {
  46:         try
  47:         {
  48:             BlammoNet.Manager.Instance.GetLog().LogMessage("Exception occurred: {0}", MessageType.Error, e.InternalException.ToString());
  49:         }
  50:         catch { throw; }
  51:     }
  52: }
If I wanted to use something like NLog or log4net, it would be about as simple, but of course I'm going to use my own logging library. Now this would log EVERY function, which could bog things down a bit so let's indicate when we want to do something by using attributes:
   1: [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
   2: public class ProfileAttribute : Attribute
   3: {
   4: }
   5:  
   6: public class ProfilerAspect : Aspect
   7: {
   8:     public ProfilerAspect()
   9:         : base()
  10:     {
  11:         Profilers = new Dictionary<string, Profiler>();
  12:     }
  13:  
  14:     public override void Setup(object Object)
  15:     {
  16:         try
  17:         {
  18:             if (Object.GetType().GetCustomAttributes(typeof(ProfileAttribute), true).Length > 0)
  19:             {
  20:                 ((IEvents)Object).Aspectus_Starting += Start;
  21:                 ((IEvents)Object).Aspectus_Ending += End;
  22:                 ((IEvents)Object).Aspectus_Exception += Exception;
  23:             }
  24:         }
  25:         catch { throw; }
  26:     }
  27:  
  28:     public void Start(object sender, Starting e)
  29:     {
  30:         try
  31:         {
  32:             if (Profilers.ContainsKey(e.MethodName))
  33:             {
  34:                 Profilers[e.MethodName] = new Profiler(e.MethodName);
  35:             }
  36:             else
  37:             {
  38:                 Profilers.Add(e.MethodName, new Profiler(e.MethodName));
  39:             }
  40:         }
  41:         catch { throw; }
  42:     }
  43:  
  44:     public void End(object sender, Ending e)
  45:     {
  46:         try
  47:         {
  48:             if (Profilers.ContainsKey(e.MethodName))
  49:             {
  50:                 Profilers[e.MethodName].Stop();
  51:                 Profilers[e.MethodName].Dispose();
  52:                 Profilers.Remove(e.MethodName);
  53:             }
  54:         }
  55:         catch { throw; }
  56:     }
  57:  
  58:     public void Exception(object sender, Aspectus.EventArgs.Exception e)
  59:     {
  60:         try
  61:         {
  62:             if (Profilers.ContainsKey(e.InternalException.TargetSite.Name))
  63:             {
  64:                 Profilers[e.InternalException.TargetSite.Name].Stop();
  65:                 Profilers[e.InternalException.TargetSite.Name].Dispose();
  66:                 Profilers.Remove(e.InternalException.TargetSite.Name);
  67:             }
  68:         }
  69:         catch { throw; }
  70:     }
  71:  
  72:     private Dictionary<string, Profiler> Profilers { get; set; }
  73: }
This aspect uses the Profile attribute, defined on a class, to indicate whether or not to profile the functions of a class. Note that it uses my rather poorly made profiler from my utility library. But it works well enough. We could even have an aspect that did this for an individual method, for example caching or validation (maybe you only want to validate when function X is called). The point is, no IL is required and we can accomplish quite a bit. Anyway, that's all there is to it. Any time Create is called, we will be able to get our sub class which is full of aspects waiting to do work for us. Pretty simple actually.
 
Anyway, I'll be releasing this code with a couple aspects as examples in the near future. In the mean time, take a look, leave feedback, and maybe come up with your own approach to AOP. Maybe you can think of something simpler or more powerful than what I have here. For now, happy coding.


Comments

david
November 08, 2011 3:21 PM

Great series, thanks.