Using Unity to do poor man’s tracing

For this post I will assume you are familiar with Dependency Injection, Inversion of Control and Unity. There are a couple of tutorials out there that show you the basics of all of these.

OK, say you have got a class Widget that others depend upon, and that you have an interface IWidget to loosen the coupling.

public interface IWidget
{
	string Tagerize(bool leetified, ref string nickName, out int skillFactor);
	void Fizz();
}

Never mind the silly names, I was on some kind of high when I wrote it. Also, the ref and out are there for a purpose. To resolve the Widget object through an IWidget reference, this is what you would do.

bool leetified = false;
int skillFactor;
string nickName = "IncredibleElmo";

IWidget tracedViaInterface = container.Resolve<IWidget>();

// Direct invocation of method
string result = tracedViaInterface.Tagerize(leetified, ref nickName, out skillFactor);

When dependencies of type IWidget are resolved in your application like above, or via a “cascaded” resolution (as part of a dependency of a dependency, and so on), you are ready to do some fancy stuff. Unity puts you in control of the creation of the depended-upon object, meaning that you can tell Unity to do cool things and impress all of your friends.

For example, say your colleagues (or whomever you want to put the blame on) haven’t done much about tracing/instrumentation facilities in the application’s code. With Unity you can do some quick instrumentation. It can give you tracing for calls to Unity-created objects. Here’s how that would go.

First, you’ll need Unity 1.2 (or higher) which is part of Enterprise Library 4.1. Since version 1.2 there is a marriage between the Policy Injection Application Block (PIAB) and Unity, essentially giving you PIAB with the ease of Unity. The marriage can be found in a Unity container extension called “Interception” inside the assembly Microsoft.Patterns.Unity.Interception.

container
	.AddNewExtension<Interception>();
	.Configure<Interception>()
	.SetInterceptorFor<IWidget>(new InterfaceInterceptor());

This additional configuration of the Unity container uses the interception mechanism to give you a man-in-the-middle object through your interface (call it a interceptor or proxy) to the actual Widget object. The interceptor can do additional work for you on all or some calls to the Widget object. You would like to trace these calls.

For the tracing to work you need a call handler that knows what to do during the interception of the call. Here’s the code for my TraceCallHandler:

public class TraceCallHandler : ICallHandler
{
	public TraceCallHandler() { }

	#region ICallHandler Members

	public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
	{
		IMethodReturn result = getNext().Invoke(input, getNext);

		// Do interception tracing
		TraceCall(input, result);

		return result;
	}

	private void TraceCall(IMethodInvocation input, IMethodReturn result)
	{
		StringBuilder builder = new StringBuilder();
		builder.AppendFormat("{0}::{1}(", input.MethodBase.ReflectedType.Name, input.MethodBase.Name);

		List<string> arguments = new List<string>();
		bool first = true;
		for (int index = 0; index < input.Arguments.Count; index++)
		{
			if (!first) builder.Append(", ");

			string paramName = input.Arguments.ParameterName(index);
			ParameterInfo info = input.Arguments.GetParameterInfo(paramName);
			builder.AppendFormat("{0} = {1}", info, input.Arguments[paramName]);

			if (info.ParameterType.IsByRef || info.IsOut)
			{
				for (int counter = 0; counter < result.Outputs.Count; counter++)
				{
					if (result.Outputs.ParameterName(counter) == info.Name)
						builder.AppendFormat("=> {0}", result.Outputs[counter]);
				}
			}

			first = false;
		}

		builder.Append(")");
		if (result.ReturnValue != null) builder.AppendFormat(" => {0}", result.ReturnValue);

		// Log string
		Log(builder.ToString());
	}

	private void Log(string text)
	{
		Debug.WriteLine(text);
	}

	public int Order { get; set; }

	#endregion
}

If you want to have a look at what this does, focus on the Invoke first. That is the action that the call handler will perform, should the interceptor object choose to call this handler. Invoke will do the actual call on our Widget target object (but it doesn’t even need to) and then trace the call using the contextual information. The TraceCall method is some fancy outputting to the Debug window (or to DbgView if you’re not debugging).

You will need to instruct the interceptor to consider calling your handler. You will need an attribute on whatever you want to handle: your interface method or the entire interface e.g.

public class TraceableAttribute : HandlerAttribute
{
	public override ICallHandler CreateHandler(IUnityContainer container)
	{
		return new TraceCallHandler();
	}
}

By applying this attribute to the traced class, the interface, one or more of its methods you should be good to go. For completeness sake I give you my Widget implementation. Feel free to use the ToBrEzAhCasing extension method in your business logic somewhere.

[Traceable]
public class Widget : MarshalByRefObject, IWidget
{
	#region IWidget Members

	public string Tagerize(bool leetified, ref string nickName, out int skillFactor)
	{
		nickName = nickName.ToBrEzAhCasing();
		skillFactor = leetified ? 1337 : 42;
		return String.Format("{0} pwns @{1}", nickName, skillFactor);
	}

	public void Fizz() { }

	#endregion
}

public static class StringExtensions
{
	public static string ToBrEzAhCasing(this string text)
	{
		int index = 0;
		return new String(text.ToCharArray().Select(c => index++ % 2 == 0 ? Char.ToUpper(c) : c).ToArray());
	}
}

The output is as follows:

IWidget::Tagerize(Boolean leetified = False, System.String& nickName = InCrEdIbLeElMo, Int32& skillFactor = 42=> 42) => InCrEdIbLeElMo pwns @42
IWidget::Fizz()

You can go all the way and include much more in this tracing. This serves just to show you how to do the interception. The rest is up to you.

In conclusion: what this boils down to is tracing through interception of calls to attributed methods (directly attributed or via an interface or their class) provided the objects have been created by the Unity container and pass through the proxy. I.e. no internal calls will be traced, nor will calls to objects that you created without the use of the container. Hey, I said it was poor man’s tracing. All you need to do is attribute your stuff.

Notes and various other kinds of disclaimers/CMA clauses:

  • in this article I described the scenario of using an interface to do interception. Wait, there are more options. It is also possible to intercept based on a MarshalByRefObject derived class or a class with virtual methods. These need different interceptor classes (TransparentProxyInterceptor and VirtualMethodInterceptor respectively). There’s more to be found here.
  • The wiring of the interception extension can be done through configuration as well.
  • You can apply matching rules in a Unity policy that will get evaluated before using the call handlers. If a match can be made (e.g. based on type, parameter of a method, some Tag attribute, …) then the call handlers of the policy will execute (or not for a mismatch).
  • You really do not need to build your own log/trace handler. The PIAB ships with a shipload of call handlers, including a LogCallHandler. This handler uses the Logging Application Block to do its logging output. The circle is complete, or whatever. I might just show how to do this in a later post.
  • Yes, there are some tiny bugs in the TraceCall method. I should not output values of ‘out’ parameters and it would be better to capture the ToString of an reference type before the call instead of only after the call has happened.
Advertisements
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s