ASP.NET MVC 2 and passing JSON objects to Actions

by ahura mazda 10. June 2011 10:39

I’ve recently made the jump to ASP.NET MVC 2 (my Godaddy host is running ASP.NET 3.5). Everything worked well until I started converting my old ASP.NET JSON Webservices to Controller Actions.

Retrieving JSON responses from the Action worked fine, but sending complex JSON objects or arrays back to the Action as parameters started failing. I started using other developers solutions which worked with complex JSON objects but then 2 types of objects fail, Enums and Arrays.

I first used Javier G. Lozano’s JsonModelBinderwhich worked for everything except I could not use it on Godaddy because the code uses Reflection to invoke an internal method in JavaScriptSerializer (throws a Security Exception).

I then used ASP.NET MVC 2 Futuresand its JsonValueProviderFactory approach. That worked for JSON arrays but not for Enums. Integers coming back from JavaScript are not converted to their .NET Enum equivalent. So I went back to Javier’s JsonModelBinder and removed the Reflection code only to find it no longer works with JSON arrays.

At my wit’s end as I needed to fix a broken site for my client, I decided to reverse engineer JavaScriptSerializer using ILSpy as Javier’s reflection code was invoking an internal method Deserialize(). That worked great; no Security exceptions and my complex JSON objects were being passed correctly.

However, I did not like this hacked solution. Fortunately, I came across James Newton-King’s Json.NET. This solves the problems of Enums and Arrays elegantly. Here’s what you need:

Server Side References

Client Side References

  • jQuery or any other library or custom Ajax code,
  • Json2 or any other Json library

Server Side Code

part 1 of 2
Create a JsonModelBinder class. I've customized Javier's JsonModelBinder.cs file to use Json.NET JsonConvert instead of Reflection:

using System.IO;
using System.Web.Mvc;
using Newtonsoft.Json;

namespace AhuraMazda.Mvc.Binders
{
	/// 
	/// Model binder that does the mapping for any JSON request or basic request
	/// 
	///	Original code from http://lozanotek.com/blog/archive/2010/04/16/simple_json_model_binder.aspx
	/// 
	/// 
	public class JsonModelBinder : DefaultModelBinder
	{
		public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
		{
			if (!IsJSONRequest(controllerContext))
			{
				return base.BindModel(controllerContext, bindingContext);
			}
			// Get the JSON data that's been posted
			var request = controllerContext.HttpContext.Request;
			var jsonStringData = new StreamReader(request.InputStream).ReadToEnd();

			return JsonConvert.DeserializeObject(jsonStringData, bindingContext.ModelMetadata.ModelType);
		}

		private static bool IsJSONRequest(ControllerContext controllerContext)
		{
			var contentType = controllerContext.HttpContext.Request.ContentType;
			return contentType.Contains("application/json");
		}
	}
}

part 2 of 2
Register your JsonModelBinder with MVC. In Global.asax, add this:

protected void Application_Start()
{
	// This custom model binder is required to allow JSON to be correctly serialised from client to server.
	// Don't worry about setting the DefaultBinder as JsonModelBinder inherits DefaultBinder and uses it accordingly.
	ModelBinders.Binders.DefaultBinder = new AhuraMazda.Mvc.Binders.JsonModelBinder();

	// Your other code goes here...
}

 

optional (for testing)

public class ServicesController : Controller
{
	public class TestModel
	{
		public int TestId {get;set;}
		public List<NestedTestModel> NestedTests {get;set;}
	}
	public class NestedTestModel
	{
		public int NestedTestId {get;set;}
		public string Name {get;set;}
	}

	[HttpPost]
	public ActionResult Test(TestModel testModel)
	{
		return Json(testModel);
	}
}

Client Side Code

In your page/view, use this example jQuery Ajax:

function testAjax()
{
	var data =
	{
		TestId: 1,
		NestedTests:
		[
			{
				NestedTestId: 2,
				Name: "two"
			},
			{
				NestedTestId: 3,
				Name: "three"
			},
		]
	};

	jQuery.ajax({
		type: "POST",
		url: "/services/Test/", // assuming ServicesController with action Test(TestModel testModel)
		data: JSON.stringify(data), // JavaScript object must be converted to Json string representation
		contentType: 'application/json', // must be application/json as JsonModelBinder checks for this content type
		dataType: "json",
		success: function (data, textStatus, jqXHR)
		{
			alert(JSON.stringify(data));
		},
		error: function (jqXHR, textStatus, errorThrown)
		{
			alert(jqXHR.responseText);
		}
	});
}

Tags: , , , ,

Add comment




  Country flag
biuquote
  • Comment
  • Preview
Loading