Click here to Skip to main content
11,714,345 members (86,276 online)
Click here to Skip to main content

Custom ASP.NET Model Binders series, Part 1: Now How

, 5 Nov 2011 CPOL 4.7K 5
Rate this:
Please Sign up or sign in to vote.
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 wan 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:

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

And the example usage:

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 tht might be of use 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:

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:

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

And register it in the global.asax:

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:

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.

[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)

Share

About the Author

Artem Smirnov
Software Developer GeekSoft
Lithuania Lithuania
No Biography provided

You may also be interested in...

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.150819.1 | Last Updated 5 Nov 2011
Article Copyright 2011 by Artem Smirnov
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid