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

2011-04-26

Simple ASP.NET MVC object serializer

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

Occasionally there is a need to give user a way to return to an exact state of the page – whether it is for giving to a co-worker or saving in bookmarks doesn’t really matter. One way is to save the state to a database – and give user ID, so we can give him an address http://…/details/1031423. Another is to provide him with an URL that contains information needed to restore the state of the page – i.e. information about the model for our ASP.NET MVC page.

There are downsides for the latter approach. For one, URL length is limited to about 2000 characters, thanks to the Internet Explorer up to version 8. IE 9 theoretically increased the limit to 4GB. Other browsers can handle far longer URL’s, so hopefully this won’t be an issue in the future.

Second problem is that it may require more work – namely, almost certainly you have to write your own modelbinder to deserialize the URL back to the object. Depending on the complexity of the object, this may be quite a lengthy task, especially as you have to verify the object as well. I will give a snippet to help with model binding in some other post.

Another downside is that long URL’s are ugly. But this is a necessary evil; a tradeoff between constantly saving lots of potentially un-needed info to the database and object serialization to GET address.

In case of a simple objects, deserialization is easy – just return from your method return RedirectToAction(“Details”, mySimpleObject); and you’re done. MVC framework is capable enough to turn your object into a querystring.

In case of very complex objects, there is a CodePlex project unbinder/unbound. It looks very neat, but I must admit I had some issues with it – also it didn’t seem to be recursive.

Why should serializer be recursive? Think of a “standard” shopping cart object – usually you will have property Client (class Person), but you may also have Payer and Recipient. Person class has one or several Address-type classes – as will your shopping cart. When you serialize your shopping cart to the URL, you want all of the above to it.

public class ModelDeserializer
{
 	private readonly RouteValueDictionary _dictionary = new RouteValueDictionary();   
	/// <summary>
	/// Recursive method
	/// </summary>
	public RouteValueDictionary ToRouteDictionary(object o, string prefix)
	 {            
	PropertyInfo[] properties = o.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
	foreach (var property in properties)
	{
		if (property.PropertyType.IsValueType || property.PropertyType.Name == "String")
		{
			var value = property.GetValue(o, BindingFlags.Public, null, null, CultureInfo.CurrentCulture);
			var defValue = property.PropertyType.IsValueType ? Activator.CreateInstance(property.PropertyType) : null;
		if (value != null && !value.Equals(defValue)) //we don't need default values
		_dictionary[string.Format("{0}{1}", prefix, property.Name)] = value;
		}
		else
			ToRouteDictionary(property.GetValue(o, null), string.Concat(prefix, property.Name, "."));
	}
	return _dictionary;
	} 
}

It should be fairly self-explanatory – and you can build on this easily yourself if you need to. You call ModelDeserializer from your controller with return RedirectToAction(“Details”, new ModelDeserializer().ToRouteDictionary(myComplexModel, string.Empty)); – and will get a nice url. However, there are some things that should be pointed out:

  • ModelDeserializer in its current form handles only public instance properties. It will not give private properties nor fields. You can enable private and static properties easily by changing BindingFlags – however, that is usually not needed. You can add fields easily yourself, using properties as an example.
  • It attempts to skip properties with null or default values, to keep the URL shorter. Remember that in your modelbinder.
  • RouteValueDictionary/MVC mishandles DateTime values – it does not turn them into CultureInfo.CurrentCulture-specific string values, instead it will do en-US MM/dd/yyyy. This is ASP.NET MVC bug, so allow for that in your modelbinder! Alternately, you could add “special” handling of DateTime – ie. using something like _dictionary[string.Format(“{0}{1}”, prefix, property.Name)] = property.PropertyType.Name == “DateTime” ? ((DateTime)value).ToString() : value;, which will turn DateTime values into culture-specific string before adding them to the RouteValueDictionary, thus avoiding the bug.
  • This code will not handle collections. It is fairly easy to add, I just didn’t need that in my particular project.
  • Remember that by default my code will also serialize properties you don’t want to be serialized – passwords, for example. Simple workaround is just to have fields instead of properties for such data.

Why is the prefix needed? With recursion, it will allow you to understand from which class property the value came from. Ie. shopping cart’s client’s address zipcode name will be Client.Address.Zipcode and payer’s zipcode field will be Payer.Address.Zipcode (as in URL – …&Payer.Address.Zipcode=0010&…  – which makes it very easy to bind later.

About these ads

1 kommentaar »

  1. […] a recent post I mentioned a small helper for model binding. The default model binder of ASP.NET MVC is fairly […]

    Pingback-viide kirjutas Generic helper for ASP.NET MVC model binding « …meie igapäevast IT’d anna meile igapäev… — 2011-05-02 @ 12:44:13 | Vasta


Selle postituse kommentaaride RSS-voog. 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

Theme: Rubric. Get a free blog at WordPress.com

Follow

Get every new post delivered to your Inbox.

Join 83 other followers