Click here to Skip to main content
15,886,724 members
Articles / Web Development / ASP.NET

Custom ASP.NET Model Binders Series. Part 1: Now How

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
5 Nov 2011CPOL3 min read 8.2K   5  
Writing testable Action Methods

Recently, I started a challenge on LinkedIn: let's write blog posts about custom model binders, view results, and other extensible stuff in ASP.NET MVC; let's do it one post a day for a week and see what happens. Presumably, it should benefit everybody, especially ASP.NET MVC newcomers.

Well, I'm two days late already, and I don't feel like doing a big post today, so today's a really simple custom Model Binder. But first, we should decide which functionality we want to move to our binders. For me, an eye opening post was Scott Hanselmann's one on his IPrincipal binder. The logic here is very simple. The Smart Guys Who Invented ASP.NET MVC wanted it to be testable. Specifically, they wanted the Action Methods to be testable. So, let's make them happy and proud of us, and let's write testable Action Methods, shall we?

There are two simple tricks that can help with it. One trick is widely adopted in general programming: Dependency Injection. You can write a service, use it as a constructor argument in your Controller, and use this service inside your method to retrieve the required value (and also to execute an action, but that's a different story). However, you can end up with 10 methods, each requiring a different service, hence 10 services injected in the constructor, and you'll have to create all 10 for testing each method.

Another option is parameter injection: you don't create a service in order to retrieve a value (and mock it in your tests), you just use your value as a parameter. The testability problem is solved, but how about the runtime behavior? Yes, we'll use custom Model Binders (and Value Providers).

Let's See Your Example

One of the common values that are used in our methods, but cannot be tested using our usual tools, is DateTime.Now. Yes, I know that Typemock Isolator can mock it now, but that's not the point. The point is that, rather than call it inside our method, we can provide it as an argument. It would be our Custom Model Binder's responsibility to look at the watch and tell us the time. (Note that we don't eliminate the untestable part, we just move it from our controller elsewhere)

Here's the code for the custom binder:

C#
public class NowBinder : IModelBinder {
	public object BindModel(
			ControllerContext controllerContext, 
			ModelBindingContext bindingContext) {
		return DateTime.Now;
	}
}

And the example usage:

C#
public ActionResult ActionUsingCurrectDateTime(
	[ModelBinder(typeof(NowBinder))] DateTime current) {..

You are encouraged to subclass the ModelBindingAttribute in order to make it more readable, but that's another story.

Doing It With A Custom Value Provider

While custom Model Binders are used more or less widely, there's another point of extensibility that might be of used here: a custom ASP.NET MVC Value Provider. This might be even more appropriate: while Model Binders are created to construct objects from data, Value Providers are the ones who retrieve this data from the outside world. Obviously, the system clock is a part of this "outside world" so, let's use a value provider:

C#
public class NowValueProvider : IValueProvider {
	public bool ContainsPrefix(string prefix) {
		return prefix == "now";
	}
 
	public ValueProviderResult GetValue(string key) {
		var now = DateTime.Now;
		if (key == "now")
			return new ValueProviderResult(
				now, 
				now.ToString(CultureInfo.CurrentCulture), 
				CultureInfo.CurrentCulture);
		return null;
	}
}

Unfortunately, using it is not that simple: you have to subclass ValueProviderFactory:

C#
public class NowValueProviderFactory : ValueProviderFactory {
	public override IValueProvider 
		GetValueProvider(ControllerContext controllerContext) {
		return new NowValueProvider();
	}
}

And register it in the global.asax:

C#
ValueProviderFactories.Factories.Add(new NowValueProviderFactory());

However, you don't have to use a custom binder now, just name your parameter "now", and it'll be automatically bound to DateTime.Now:

C#
public ActionResult ActionUsingNowValueProvider(DateTime now)

In addition, whenever your model has a property named "Now", it'll be filled automatically by the default model binder.

Isn't it sweet?

Can We Test That It Works?

Previously, I said that we're moving the untestable part to a different place. The point is, we have moved it to our infrastructure, leaving our controller clean of infrastructure concerns. However, with the latest Ivonna for MVC, we can test that the whole thing works together correctly.

C#
[Test]
public void TestValueProvider() {
	var result = new TestSession()
		.Get("/Sample/ActionUsingNowValueProvider");
	var model = (DateTime) result.Model;
	Assert.AreApproximatelyEqual (
		DateTime.Now, 
		model, 
		TimeSpan.FromSeconds(1)) ;
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer GeekSoft
Lithuania Lithuania
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --