Castle Dynamic Proxy tutorial part IX: Interface proxy with target

This is part nine of my tutorial on Castle Dynamic Proxy.

As cool as interface proxies without target are, most of the time you’d have an existing implementation under interface. Still, there are many useful scenarios, where you would want to use proxies. AOP stuff comes to mind, but not only.

Let’s say you’re using a library from some vendor that among other things exposes an interface like the following 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 implements 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 representation of date and time, for example “10/11/2009 5:32:11 AM” and return UTC value for its hour, minute, or second.

Two things to note about this example. One is that the implementation has few bugs, second one – the class is sealed, so you can’t inherit from it.

First bug, and the most obvious is, it does not check for null reference, so when you pass it null string, it will throw. It also does not validate the format of the string in any way (it should rather use DateTime.TryParse method).

More subtle bug lies in the fact, that output value from each of the methods has to represent UTC time. Input value on the other hand, may represent any time zone (for example “10/11/2009 9:32:11 AM -04:30”). The implementation will produce invalid values for time zones other than UTC, but since the vendor was located in UTC, they didn’t test for it. Actually it’s also due to DateTime’ inability to represent time zone information. To fix this DateTimeOffset a.k.a. DateTime2 structure was introduced.

There’s also another bug related to globalization – the string may be in different form for different culture, for example, in Poland above Venezuelan date would look like this: “2009-10-11 09:32:11 -04:30”. However, for the sake of simplicity we will ignore that issue.

Now, let’s assume the vendor of the component won on the lottery, closed his business, and moved to Brisbane. In other words, don’t expect him to fix the bugs.

This is one example when proxy might help. To be fair, for such a trivial class better option would be to roll your own implementation of the interface from scratch. However let’s assume the TimeHelper class is much more complicated and we do care about the rest of it’s non-buggy behavior.

We identified 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 surprisingly passes (this only proves you should run your tests expecting them to fail before you write the implementation that would make them pass). I was really surprised to see that, which in my opinion only shows how inconsistent DateTime is. Anyway, we still have three tests to fix, so back to our business.

Since we use the class by the interface, the fact that it’s sealed is not a problem. That’s one major difference between class proxy we worked with in initial parts of the tutorial, and interface proxy with target – interface proxy can use sealed class instance as target implementation. That’s the result of the way such proxy is implemented. DynamicProxy creates a new type that implements given interface, and which forwards the calls to given target object. The only thing both types have in common is they both implement the same interface.

We have three major issues to fix:

One is to handle calls when null is passed to the method. We do it the usual way, by writing a simple 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 invocation of the actual method, and return right back to the caller returning 0.

Other two issues, are dealing with input. Since DateTime is incapable of understanding time zones, we must intercept calls to the methods that can produce wrong values when input string represents some other time zone than UTC. So far there are no time zones that differ by mere seconds, so that leaves us with two methods to fix. We might write an interceptor 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 succeed, we switch it with its UTC value. If we can’t parse the string we let the underlying implementation (that is the sealed TimeHelper class) handle this. We than catch exception the implementation may throw and instead provide default return value.

We’re now only left with a way to select appropriate interceptors for intercepted methods. For that, we create 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();
	}
}

Selector checks if method is one of known methods that are prone to time zone bug, and appends appropriate interceptors at the beginning of given interceptors array. Remember that order is important. That’s why CheckNullInterceptor is put on the first place, so that every following interceptor does not have to check if argument is null.

With all that we’re almost done. However it would probably be a good idea to encapsulate all the proxy creation 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 fixture 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 simplified example, but hopefully by now you understand how dynamic proxies with target work, how they’re different from other kinds of proxies we talked about so far (class proxies, and interface proxies without target) and how and when you can utilize them. If not, or you have any questions, feel free to leave a comment.

Technorati Tags: , ,

1 Thought.

Comments are closed.