Castle Dynamic Proxy tutorial part IX: Interface proxy with target

This is part nine of my tuto­r­ial on Cas­tle Dynamic Proxy.

As cool as inter­face prox­ies with­out tar­get are, most of the time you’d have an exist­ing imple­men­ta­tion under inter­face. Still, there are many use­ful sce­nar­ios, where you would want to use prox­ies. AOP stuff comes to mind, but not only.

Let’s say you’re using a library from some ven­dor that among other things exposes an inter­face like the fol­low­ing along with its implementation:

namespace ThirtPartyLibrary
{
	public interface ITimeHelper
	{
		int GetHour(string dateTime);
		int GetMinute(string dateTime);
		int GetSecond(string dateTime);
	}
}

There’s also a class in the library that imple­ments the interface:

namespace ThirtPartyLibrary
{
	public sealed class TimeHelper : ITimeHelper
	{
		public int GetHour(string dateTime)
		{
			DateTime time = DateTime.Parse( dateTime );
			return time.Hour;
		}

		public int GetMinute(string dateTime)
		{
			DateTime time = DateTime.Parse(dateTime);
			return time.Minute;
		}

		public int GetSecond(string dateTime)
		{
			DateTime time = DateTime.Parse(dateTime);
			return time.Second;
		}
	}
}

Each method accepts string rep­re­sen­ta­tion of date and time, for exam­ple “10/11/2009 5:32:11 AM” and return UTC value for its hour, minute, or second.

Two things to note about this exam­ple. One is that the imple­men­ta­tion has few bugs, sec­ond one – the class is sealed, so you can’t inherit from it.

First bug, and the most obvi­ous is, it does not check for null ref­er­ence, so when you pass it null string, it will throw. It also does not val­i­date the for­mat of the string in any way (it should rather use DateTime.TryParse method).

More sub­tle bug lies in the fact, that out­put value from each of the meth­ods has to rep­re­sent UTC time. Input value on the other hand, may rep­re­sent any time zone (for exam­ple “10/11/2009 9:32:11 AM –04:30”). The imple­men­ta­tion will pro­duce invalid val­ues for time zones other than UTC, but since the ven­dor was located in UTC, they didn’t test for it. Actu­ally it’s also due to Date­Time’ inabil­ity to rep­re­sent time zone infor­ma­tion. To fix this Date­Time­Off­set a.k.a. DateTime2 struc­ture was introduced.

There’s also another bug related to glob­al­iza­tion – the string may be in dif­fer­ent form for dif­fer­ent cul­ture, for exam­ple, in Poland above Venezue­lan date would look like this: “2009-10–11 09:32:11 –04:30”. How­ever, for the sake of sim­plic­ity we will ignore that issue.

Now, let’s assume the ven­dor of the com­po­nent won on the lot­tery, closed his busi­ness, and moved to Bris­bane. In other words, don’t expect him to fix the bugs.

This is one exam­ple when proxy might help. To be fair, for such a triv­ial class bet­ter option would be to roll your own imple­men­ta­tion of the inter­face from scratch. How­ever let’s assume the Time­Helper class is much more com­pli­cated and we do care about the rest of it’s non-buggy behavior.

We iden­ti­fied three bugs, we’d like to fix for this class, let’s now write tests to express our requirements:

public class TimeFixTests
{
	private ITimeHelper _sut;

	public TimeFixTests()
	{
		_sut = new TimeHelper();
	}

	[Fact]
	public void GetMinute_should_return_0_for_null()
	{
		int minute = _sut.GetMinute(null);
		int second = _sut.GetSecond(null);
		int hour = _sut.GetHour(null);
		Assert.Equal(0, minute);
		Assert.Equal(0, second);
		Assert.Equal(0, hour);
	}

	[Fact]
	public void Fixed_GetHour_properly_handles_non_utc_time()
	{
		var dateTimeOffset = new DateTimeOffset(2009, 10, 11, 09, 32, 11, TimeSpan.FromHours(-4.5));
		DateTimeOffset utcTime = dateTimeOffset.ToUniversalTime();
		string noUtcTime = dateTimeOffset.ToString();
		int utcHour = _sut.GetHour(noUtcTime);
		Assert.Equal(utcTime.Hour, utcHour);
	}

	[Fact]
	public void Fixed_GetMinute_properly_handles_non_utc_time()
	{
		var dateTimeOffset = new DateTimeOffset(2009, 10, 11, 09, 32, 11, TimeSpan.FromMinutes(45));
		DateTimeOffset utcTime = dateTimeOffset.ToUniversalTime();
		string noUtcTime = dateTimeOffset.ToString();

		int utcMinute = _sut.GetMinute(noUtcTime);
		Assert.Equal(utcTime.Minute, utcMinute);
	}

	[Fact]
	public void Fixed_GetHour_hadles_entries_in_invalid_format()
	{
		int result = _sut.GetHour("BOGUS ARGUMENT");
		Assert.Equal(0, result);
	}
}

If we run the tests now, they will fail, except for the third one which sur­pris­ingly passes (this only proves you should run your tests expect­ing them to fail before you write the imple­men­ta­tion that would make them pass). I was really sur­prised to see that, which in my opin­ion only shows how incon­sis­tent Date­Time is. Any­way, we still have three tests to fix, so back to our business.

Since we use the class by the inter­face, the fact that it’s sealed is not a prob­lem. That’s one major dif­fer­ence between class proxy we worked with in ini­tial parts of the tuto­r­ial, and inter­face proxy with tar­get – inter­face proxy can use sealed class instance as tar­get imple­men­ta­tion. That’s the result of the way such proxy is imple­mented. Dynam­icProxy cre­ates a new type that imple­ments given inter­face, and which for­wards the calls to given tar­get object. The only thing both types have in com­mon is they both imple­ment the same interface.

We have three major issues to fix:

One is to han­dle calls when null is passed to the method. We do it the usual way, by writ­ing a sim­ple interceptor:

internal class CheckNullInterceptor : IInterceptor
{
	public void Intercept(IInvocation invocation)
	{
		if( invocation.Arguments[ 0 ] == null )
		{
			invocation.ReturnValue = 0;
			return;
		}
		invocation.Proceed();
	}
}

When a null value is passes, we want to skip the invo­ca­tion of the actual method, and return right back to the caller return­ing 0.

Other two issues, are deal­ing with input. Since Date­Time is inca­pable of under­stand­ing time zones, we must inter­cept calls to the meth­ods that can pro­duce wrong val­ues when input string rep­re­sents some other time zone than UTC. So far there are no time zones that dif­fer by mere sec­onds, so that leaves us with two meth­ods to fix. We might write an inter­cep­tor like this:

internal class AdjustTimeToUtcInterceptor:IInterceptor
{
	public void Intercept( IInvocation invocation )
	{
		var argument = (string)invocation.Arguments[0];
		DateTimeOffset result;
		if (DateTimeOffset.TryParse(argument, out result))
		{
			argument = result.UtcDateTime.ToString();
			invocation.Arguments[ 0 ] = argument;
		}
		try
		{
			invocation.Proceed();
		}
		catch( FormatException )
		{
			invocation.ReturnValue = 0;
		}
	}
}

We try to parse the date and time out of given string, and if we suc­ceed, we switch it with its UTC value. If we can’t parse the string we let the under­ly­ing imple­men­ta­tion (that is the sealed Time­Helper class) han­dle this. We than catch excep­tion the imple­men­ta­tion may throw and instead pro­vide default return value.

We’re now only left with a way to select appro­pri­ate inter­cep­tors for inter­cepted meth­ods. For that, we cre­ate a selector.

internal class TimeFixSelector : IInterceptorSelector
{
	private static readonly MethodInfo[] methodsToAdjust =
		new[]
		{
			typeof(ITimeHelper).GetMethod("GetHour"),
			typeof(ITimeHelper).GetMethod("GetMinute")
		};
	private CheckNullInterceptor _checkNull = new CheckNullInterceptor();
	private AdjustTimeToUtcInterceptor _utcAdjust = new AdjustTimeToUtcInterceptor();

	public IInterceptor[] SelectInterceptors(Type type, MethodInfo method, IInterceptor[] interceptors)
	{
		if (!methodsToAdjust.Contains(method))
			return new IInterceptor[] { _checkNull }.Union( interceptors ).ToArray();
		return new IInterceptor[] { _checkNull, _utcAdjust }.Union( interceptors ).ToArray();
	}
}

Selec­tor checks if method is one of known meth­ods that are prone to time zone bug, and appends appro­pri­ate inter­cep­tors at the begin­ning of given inter­cep­tors array. Remem­ber that order is impor­tant. That’s why Check­NullInter­cep­tor is put on the first place, so that every fol­low­ing inter­cep­tor does not have to check if argu­ment is null.

With all that we’re almost done. How­ever it would prob­a­bly be a good idea to encap­su­late all the proxy cre­ation logic in its own class:

public class TimeFix
{
	private ProxyGenerator _generator = new ProxyGenerator();
	private ProxyGenerationOptions _options = new ProxyGenerationOptions { Selector = new TimeFixSelector() };

	public ITimeHelper Fix(ITimeHelper item)
	{
		return (ITimeHelper)_generator.CreateInterfaceProxyWithTarget(typeof(ITimeHelper), item, _options);
	}
}

Now, to make our tests pass, we need to update the test fix­ture constructor:

public TimeFixTests()
{
	var fix = new TimeFix();
	_sut = fix.Fix(new TimeHelper());
}

And all the tests should now pass.

dptutorial_9_all_green

That was a sim­pli­fied exam­ple, but hope­fully by now you under­stand how dynamic prox­ies with tar­get work, how they’re dif­fer­ent from other kinds of prox­ies we talked about so far (class prox­ies, and inter­face prox­ies with­out tar­get) and how and when you can uti­lize them. If not, or you have any ques­tions, feel free to leave a comment.

Tech­no­rati Tags: , ,