Json serialization – RPC style in MVC

<< JSON serialization problems and solution in MVC3 | Home | Of Image Exif, PropertyItem and reflection >>

In a previous posting, I discussed replacing the stock MVC serializer used for the JsonResult exposed when you use the controller method Json(..) instead of View.

This was all find and dandy. But how – you may wonder – can I call actions that contain complex parameters? Do I need a special binder? Should I write my own?

The answer is mostly "no". As Phil Hack blogged, the ASP.NET MVC framework already contains value providers that take care of that for you. All you need to do is ensure that your Javascript calls your action using some very specific header and mime types.

Here is a function that may help you

1: <script type="text/javascript">

2: var postToAction = function (sender) {

3: var json = $('textarea#' + sender.data).val;

4: $("textarea#myResponse").val('working...');

5: $.ajax({

6: url: document.location.href,

7: type: 'POST',

8: dataType: 'json',

9: data: json,

10: contentType: 'application/json; charset=utf-8',

11: success: function (data) {

12: var replyText = JSON.stringify(data);

13: $("textarea#myResponse").val(replyText);

14: }

15: });

16: };

17:

The special sauce here is the dataType and contentType together with the POST method. The rest is pretty much how jQuery wants to be called.

On line 6 you will note that I'm POSTing to the same URL as the browser is on – you may want to fiddle with that.

We  call this function by hooking up a button, something like:

1: $("button#myButtonId").click('myTextAreaWithJsonContent', postToAction);

In your own implementation you will no doubt compose some object in JavaScript, or create a Json object. In the code above, I'm simply using Json formatted string: Line 3 gets the value from a text area element. If you compose a complex object and want to send it, you will need to convert it into a Json string. jQuery 1.5.1 – which I happen to use – already contains this facility, JSON.stringify(x) would do the trick. You can see it here used  on line 12, for the inbound response which I simply stringify and hydrate a response text area. But this is only demo code – you will wire up the success (and failure function- right?) to your own logic.

But back to the core issue: Yes, MVC supports submitting Json and hydrating my controller action, but how do I persuade it to use my custom chosen super duper serializer rather than the stock one?

The answer is: Create a custom ValueProviderFactory and instantiate your favorite serializer in there. A bit of inspection on reveals the stock implementation.

Here's a modified version, which isolates the serialization in a clear way:

1: public class MyJsonValueProviderFactory: ValueProviderFactory

2: {

3: public override IValueProvider GetValueProvider(ControllerContext controllerContext)

4: {

5: if (controllerContext == null)

6: {

7: throw new ArgumentNullException("controllerContext");

8: }

9:

10: if (!controllerContext.HttpContext.Request.ContentType.StartsWith(

11: "application/json", StringComparison.OrdinalIgnoreCase))

12: {

13: return null;

14: }

15:

16:

17: object value = Deserialize(controllerContext.RequestContext.HttpContext.Request.InputStream);

18:

19: if (value == null)

20: {

21: return null;

22: }

23:

24: var bag = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

25:

26: PopulateBag(bag, string.Empty, value);

27:

28: return new DictionaryValueProvider<object>(bag, CultureInfo.CurrentCulture);

29: }

30:

31: private static object Deserialize(Stream stream)

32: {

33: string str = new StreamReader(stream).ReadToEnd;

34:

35: if (string.IsNullOrEmpty(str))

36: {

37: return null;

38: }

39:

40: var serializer = new JavaScriptSerializer(new MySpecialTypeResolver);

41:

42: return serializer.DeserializeObject(str);

43: }

44:

45: private static void PopulateBag(Dictionary<string, object> bag, string prefix, object source)

46: {

47: var dictionary = source as IDictionary<string, object>;

48: if (dictionary!= null)

49: {

50: foreach (var entry in dictionary)

51: {

52: PopulateBag(bag, CreatePropertyPrefix(prefix, entry.Key), entry.Value);

53: }

54: }

55: else

56: {

57: var list = source as IList;

58: if (list!= null)

59: {

60: for (int i = 0; i < list.Count; i++)

61: {

62: PopulateBag(bag, CreateArrayItemPrefix(prefix, i), list[i]);

63: }

64: }

65: else

66: {

67: bag[prefix] = source;

68: }

69: }

70: }

71:

72: private static string CreatePropertyPrefix(string prefix, string propertyName)

73: {

74: if (!string.IsNullOrEmpty(prefix))

75: {

76: return (prefix + "." + propertyName);

77: }

78: return propertyName;

79: }

80:

81: private static string CreateArrayItemPrefix(string prefix, int index)

82: {

83: return (prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]");

84: }

85: }

It all really boils down to the same ceremony as the default implementation, except on line 40 and 42 we now get to use our own special serializer. Woot!

To use this instead of the built in one, you will modify your global.asax.cs Application_Start to include something like

1: var existing = ValueProviderFactories.Factories.FirstOrDefault(f => f is JsonValueProviderFactory);

2: if (existing!= null)

3: {

4: ValueProviderFactories.Factories.Remove(existing);

5: }

6: ValueProviderFactories.Factories.Add(new MyJsonValueProviderFactory);

where the built in one gets removed and my custom one gets added. Pretty straightforward.

With this technique and the one described in my previous post, you are ready to use the full power of MVC as an API supporting a nice strongly typed parameter for the back end developer and supporting fully customizable JSON in and out of methods. No real need for other frameworks or technologies for the serving jQuery needs.

Depending on your methods, you may even get away with one set of actions serving both form posts and jQuery invocation.

Happy coding!

Monday, January 16, 2012 6:42 PM

This article is part of the GWB Archives. Original Author: Nuri Halperin

New on Geeks with Blogs

  • We Won The One Award I Actually Care About

    Full Scale made the Inc. 5000 for the fifth year straight, the 12th listing across my three companies. Here is why the one award you cannot buy is worth stopping for.

  • Your Customers Build the Features Now

    I let a tool I liked sit dead for a year rather than build the features I wanted. An MCP server meant I never had to, and your customers can do the same to your product.

  • Get the Size of a Directory in Linux the Easy Way

    du -sh for the quick answer, ncdu for the cleanup, df for the disk itself: every command for checking directory size in Linux, plus why du and df never agree.

  • Vim Search and Replace: The Ultimate Guide

    One :%s command replaces every match in a file before a find dialog would even open. The Vim substitute patterns worth the muscle memory: flags, ranges, capture groups, and multi-file edits.