…meie igapäevast IT’d anna meile igapäev…

2011-05-02

Generic helper for ASP.NET MVC model binding

Filed under: ASP.NET MVC — Sander @ 12:41:14
Tags: , , ,

In a recent post I mentioned a small helper for model binding. The default model binder of ASP.NET MVC is fairly reasonable – it can handle input from small forms very well. But what if you have highly complex data entry forms? Or forms where additional input fields are created on the fly by JavaScript? Or very complex view models with dictionaries, lists and so forth?

You will have to write a binder of your own. It is a tedious and boring job – you have to be careful to get all the field names exactly right, allow for special cases such as (idiotic) ASP.NET MVC checkboxes and so forth. Not to forget about the possibility of user fiddling with the data – changing field names, hidden values and so forth.

One thing I heavily recommend is to keep your model binding and validation completely apart. Not only makes it the code better structured and logical, but splitting two dead boring components into two workflows allows you to fiddle more interesting things between coding them.

“Standard” modelbinder looks something like:

if (controllerContext.HttpContext.Request["Client_Address_Commune"] != null){   
	long countyCode;    
	if (long.TryParse(controllerContext.HttpContext.Request["Client_Address_Commune"], out countyCode))
		address.Commune = countyCode;
}  
if (controllerContext.HttpContext.Request["Client_Address_County"] != null){
	long countryCode;
	if (long.TryParse(controllerContext.HttpContext.Request["Client_Address_County"], out countryCode))
		address.County = countyCode;
}

As you can see, a lot of repetitive, highly boring code. So I thought – how can I reduce the repetitiveness, so I wouldn’t have to spend so much with this boring crap? (sidenote: laziness is the moving force in coding).

It has been my dream to write a better modelbinder for ASP.NET MVC – reflection-based model binder, which can handle sublasses and complex models. Alas, I haven’t had time for that – and probably never will. But I did code a simple generic binder helper:

public class BinderHelper
	{
		private readonly ControllerContext _controllerContext;
                  public BinderHelper(ControllerContext controllerContext)
		{
			_controllerContext = controllerContext;
		}
                  public T Get<T>(string keyName)
		{
			if (string.IsNullOrEmpty(_controllerContext.HttpContext.Request[keyName]))
				return default(T);   
                           var value = _controllerContext.HttpContext.Request[keyName];
                           var t = typeof(T).ToString();
                           switch (t)
			{
				case "System.Boolean":
					bool b;
					if (bool.TryParse(value, out b))
						return (T)(object)b;
					return (T)(object)(value == "on");
				case "System.Int32":
					int i;
					int.TryParse(value, out i);
					return (T)(object)i;
				case "System.Decimal":
					decimal d;
					decimal.TryParse(value, out d);
					return (T)(object)d;
				case "System.Int64":
					long l;
					long.TryParse(value, out l);
					return (T)(object)l;
				case "System.String":
					return (T)(object)(string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim());
				case "System.DateTime":
					DateTime date;
					DateTime.TryParseExact(value,
						CultureInfo.CurrentCulture.DateTimeFormat.GetAllDateTimePatterns(),
						CultureInfo.CurrentCulture,
						DateTimeStyles.AllowInnerWhite,
						out date);
					return (T)(object)date;
				default:
					throw new ApplicationException("BinderHelper doesn't know how to parse '" + t + "'.");
			}
		}
}

Using BinderHelper is very easy:

public class AddressBinder : IModelBinder
 {        
	private BinderHelper _binder;
	public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
	{    
		var  binder = new BinderHelper(controllerContext);   var model = new Address
		 {
		Commune = binder.Get<long>("Client_Address_County"),
		Country = binder.Get<long>("Client_Address_Country"),
		StreetAddress = binder.Get<string>("Client_Address_StreetAddress"),
		...                                
		};   return model;
	}
}

If your ControllerContext doesn’t have the appropriate value, BinderHelper will return the default value for the data type (return default(T); ). Otherwise it will either use TryParse() to get the value – or if parsing fails, return the default value again.

As you can see, the types supported by BinderHelper are fairly limited – that is just what I needed for my particular project. You will probably want to add double and float – and possibly char, short – or even more rarely used types, such as ulong, byte and so forth. Adding them as you need should be fairly easy using existing types as blueprint.

One thing to note is that Request[keyname] uses HttpContext.Request.Params – which “gets a combined collection of QueryString, Form, ServerVariables, and Cookies items.”. In effect, this means that item with the same key may exist in the collection many times and using Request[keyname] returns first value. If you have &startdate=2011-01-01 in your URL and you post a form which contains field named startdate with value 2011-12-31, then using Request[keyname] will get 2011-01-01, as QueryString items are before the form values.

Almost certainly you wanted the form value, not QueryString one. This is a “by design” issue of ASP.NET , not a ModelBinder bug. There is an easy fix for this, though. If it is a problem for you, modify BinderHelper not to accept ControllerContext, but NameValueCollection instead – and in your modelbinder, pass the required item collection to the BinderHelper when initializing the class. I.e. instead of ControllerContext, pass ControllerContext.HttpContext.Request.QueryString or ControllerContext.HttpContext.Request.Form NameValueCollection instead, whichever you need in that particular case. You can still use ControllerContext.HttpContext.Request.Params, if you need the combined collection.

1 kommentaar »

  1. […] Binding enums in the modelbinder is a just bit different than other values, so I wrote another small method for enums into my BinderHelper: […]

    Pingback-viide kirjutas ASP.NET MVC: Enum binding helper « …meie igapäevast IT’d anna meile igapäev… — 2011-05-12 @ 12:32:09 | Vasta


RSS feed for comments on this post. TrackBack URI

Lisa kommentaar

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 / Muuda )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s

Blog at WordPress.com.