Tuesday 29 July 2008

A Dose Of Code

While developing Ivonna, I often have to figure out how various stuff in System.Web works. Sometimes Reflectoring it is enough; however, there are some particularly gigantic methods, full of loops and branches, that are hard to figure out. In any case, Reflector gives a static picture, while I need to see it live. Sure now you can download the source and debug through it (although I've never managed), but the real hackers don't use debuggers, preferring asserts and console output.

Anyway, I needed to inject some code (or stop the debugger) at some particular method call in some assembly I don't have the code of. I've been using the TypeMock's MockMethodCalled event for it. After a while, it became tedious, so I decided to put some repetitive code into a class. Soon I found that it could have a couple of more useful features, but I wanted it to be really simple, and I didn't need much, so I'm keeping it to one class, three methods, three relaxed evenings of development.

How does it work.

  1. Create an instance of Njector using the target type (this is something like creating a mocked instance).
  2. Add one or more injected pieces of code (something similar to adding expectations).
  3. Call the main code, in which an instance of the target type is created, and the target methods are called.
  4. The injected pieces are invoked before, after, or instead of the target methods. You also gain access to the target instance, target method parameters, and return value (in case you use the After injection).


Example
This is something that I actually used to figure out how the System.Web.HttpMultipartContentTemplateParser class parses the input using the ParseIntoElementList method. This method has several loops and branches, so it's not easy to find out what's happening given a particular piece of data. What you see here is just the preparation; after that I prepare the input data and call the framework code using Ivonna, but that's outside the scope of this post. GetPrivateField() is an utility extension method doing guess what.

var web = Assembly.GetAssembly(typeof(System.Web.HttpRequest));

var parserType = web.GetType("System.Web.HttpMultipartContentTemplateParser", true, true);

var inject = new Njector(parserType);

inject.After("GetNextLine", null, delegate(object target, MethodBase method, object[] parameters, object retValue) {

Console.WriteLine("GetNextLine: {0}", retValue);

Console.WriteLine("Pos={0}", target.GetPrivateField("_pos"));

Console.WriteLine("line={0}", target.GetPrivateField("_lineStart"));

Console.WriteLine("partDataStart={0}", target.GetPrivateField("_partDataStart"));

});

inject.After("AtBoundaryLine", null, delegate(object target, MethodBase method, object[] parameters, object retValue) {

Console.WriteLine("AtBoundaryLine: {0}", retValue);

});

inject.After("AtEndOfData", null, delegate(object target, MethodBase method, object[] parameters, object retValue) {

Console.WriteLine("AtEndOfData: {0}", retValue);

});

inject.After("ParsePartHeaders", null, delegate( object target, MethodBase method,object[] parameters, object retValue) {

Console.WriteLine("ParsePartHeaders");

});

inject.Before("ParsePartData", null, delegate(object target, MethodBase method, object[] parameters, object retValue) {

Console.WriteLine("ParsePartData Before");

Console.WriteLine("Pos={0}", target.GetPrivateField("_pos"));

Console.WriteLine("line={0}", target.GetPrivateField("_lineStart"));

//System.Diagnostics.Debugger.Break();

});

inject.After("ParsePartData", null, delegate(object target, MethodBase method, object[] parameters, object retValue) {

Console.WriteLine("ParsePartData After");

Console.WriteLine("Pos={0}", target.GetPrivateField("_pos"));

Console.WriteLine("line={0}", target.GetPrivateField("_lineStart"));

Console.WriteLine("partDataLength={0}", target.GetPrivateField("_partDataLength"));

});

//Prepare the data

//Do the POST

//The framework creates an instance of System.Web.HttpMultipartContentTemplateParser and calls its ParseIntoElementList method,

//which in turn calls the methods like GetNextLine, AtBoundaryLine, etc.

//After the GetNextLine method is called, our code is executed, and we're able to see the result.


Download the source here.

Update: there's a much better project now, called CThru, developed by TypeMock.