Rest For ASP.NET MVC isolates the MVC developer from the concern of rendering data in machine readable formats for wire transmission. One nice thing about it is how easy it is to go in and customize or replace the handling for any given format. In this post I will walk you through replacing the built-in Json format handler (built on DataContractJsonSerializer from System.ServiceModel.Web.dll) with one that instead uses the Json.NET library (this project is hosted here on Codeplex).
First of all download and unzip Json.NET (I used the Beta 4 drop which you can find here).
Then download and unzip the Rest for ASP.NET MVC sample (you can find it here) and open the Product\Product.sln solution in Visual Studio.
We will be modifying the sdk\MovieApp\MovieApp_EdmSample project which already shows a similar technique but uses the JavaScriptSerializer, so make sure you can run that project.
If you set this project as default and hit F5, your browser should pop up and display the following page:
Now, if you had control over the Accept header, you could ask for a json representation of this same data (a collection of movies), but let’s just go and hit the Json button at the top right, this will return “application/json” content which the browser doesn’t know how to render, so let’s save it to disk and open it, it should look like this:
[{"Id":1,"Title":"Star Wars","Director":"Lucas","DateReleased":"\/Date(251884800000)\/","EntityState":2,"EntityKey":{"EntitySetName":"MovieSet","EntityContainerName":"MoviesDBEntities","EntityKeyValues":[{"Key":"Id","Value":1}],"IsTemporary":false}},{"Id":2,"Title":"Memento","Director":"Nolan","DateReleased":"\/Date(1038988800000)\/","EntityState":2,"EntityKey":{"EntitySetName":"MovieSet","EntityContainerName":"MoviesDBEntities","EntityKeyValues":[{"Key":"Id","Value":2}],"IsTemporary":false}},{"Id":3,"Title":"Pulp Fiction","Director":"Tarantino","DateReleased":"\/Date(982483200000)\/","EntityState":2,"EntityKey":{"EntitySetName":"MovieSet","EntityContainerName":"MoviesDBEntities","EntityKeyValues":[{"Key":"Id","Value":3}],"IsTemporary":false}},{"Id":4,"Title":"Raiders","Director":"Spielberg","DateReleased":"\/Date(251020800000)\/","EntityState":2,"EntityKey":{"EntitySetName":"MovieSet","EntityContainerName":"MoviesDBEntities","EntityKeyValues":[{"Key":"Id","Value":4}],"IsTemporary":false}}]
Now that we’ve verified that everything works, let’s close the browser and move to writing some code.
Open the Infrastructure folder and find the JavaScriptSerializerFormatHandler.cs file. Right click on it, copy and paste it in the same folder, you should see the following:
You will also start seeing a bunch of build errors because we now have duplicate copies of the same classes, we’ll fix that in a minute. Rename the file to JsonNetFormatHandler.cs and open it. Find an replace JavaScriptSerializer with JsonNet so we end up with the a matching class name.
Now add a reference to the Json.NET library, we’ll need Newtonsoft.Json.dll, and replace the using statement for JavaScriptSerializer “using System.Web.Script.Serialization;” with the Json.NET equivalent “using Newtonsoft.Json;”.
In the JsonNetFormatHandler we’ll need to modify the Deserialize method to use the JsonConvert.DeserializeObject API. This is pretty trivial, it’s a one line single method call! Your method should now look like the following:
public object Deserialize(ControllerContext controllerContext, ModelBindingContext bindingContext, ContentType requestFormat)
{
string input = new StreamReader(controllerContext.HttpContext.Request.InputStream).ReadToEnd();
return JsonConvert.DeserializeObject(input, bindingContext.ModelType);
}
Notice that the first line hasn’t changed. If Json.NET were to add an API overload that reads its input from a Stream rather than from a string, this whole method could be a single line of code!
To fix the serialization path, let’s move to the JsonNetActionResult.ExecuteResult() method were we’ll need to cleanup some code that we don’t need anymore and use the JsonConvert.SerializeObject API. This is also pretty trivial, your method is now a couple lines shorter and look like the following:
public override void ExecuteResult(ControllerContext context)
{
Encoding encoding = Encoding.UTF8;
if (!string.IsNullOrEmpty(this.ContentType.CharSet))
{
try
{
encoding = Encoding.GetEncoding(this.ContentType.CharSet);
}
catch (ArgumentException)
{
throw new HttpException((int)HttpStatusCode.NotAcceptable, string.Format(CultureInfo.CurrentCulture, "Format {0} not supported", this.ContentType));
}
}
this.ContentType.CharSet = encoding.HeaderName;
context.HttpContext.Response.ContentType = this.ContentType.ToString();
string json = JsonConvert.SerializeObject(this.Data, Formatting.Indented);
byte[] bytes = encoding.GetBytes(json);
context.HttpContext.Response.OutputStream.Write(bytes, 0, bytes.Length);
}
Notice that the change is again tiny. If Json.NET were to add an API overload that writes the output to a Stream rather than converting to a string, this method could be further simplified! Also notice that I used Formatting.Indented, this isn’t required, but it has the nice side effect of helping us see a difference when we test this.
The last thing we need to do is to register this new handler as the one the system will use for Json. To do so is trivial, just open the Global.asax file and replace the type used in the MyFormatManager contructor for jsonHandler from JavaScriptSerializerFormatHandler to JsonNetFormatHandler. The code will now look as follows:
public MyFormatManager()
{
XmlFormatHandler xmlHandler = new XmlFormatHandler();
JsonNetFormatHandler jsonHandler = new JsonNetFormatHandler();
this.RequestFormatHandlers.Add(xmlHandler);
this.RequestFormatHandlers.Add(jsonHandler);
this.ResponseFormatHandlers.Add(xmlHandler);
this.ResponseFormatHandlers.Add(jsonHandler);
}
We’re done. Let’s hit F5, and when we hit the Json button and save to disk, the data we ge will look like the following:
[
{
"Id": 1,
"Title": "Star Wars",
"Director": "Lucas",
"DateReleased": "\/Date(251884800000-0800)\/",
"EntityKey": {
"EntitySetName": "MovieSet",
"EntityContainerName": "MoviesDBEntities",
"EntityKeyValues": [
{
"Key": "Id",
"Value": 1
}
]
}
},
{
"Id": 2,
"Title": "Memento",
"Director": "Nolan",
"DateReleased": "\/Date(1038988800000-0800)\/",
"EntityKey": {
"EntitySetName": "MovieSet",
"EntityContainerName": "MoviesDBEntities",
"EntityKeyValues": [
{
"Key": "Id",
"Value": 2
}
]
}
},
{
"Id": 3,
"Title": "Pulp Fiction",
"Director": "Tarantino",
"DateReleased": "\/Date(982483200000-0800)\/",
"EntityKey": {
"EntitySetName": "MovieSet",
"EntityContainerName": "MoviesDBEntities",
"EntityKeyValues": [
{
"Key": "Id",
"Value": 3
}
]
}
},
{
"Id": 4,
"Title": "Raiders",
"Director": "Spielberg",
"DateReleased": "\/Date(251020800000-0800)\/",
"EntityKey": {
"EntitySetName": "MovieSet",
"EntityContainerName": "MoviesDBEntities",
"EntityKeyValues": [
{
"Key": "Id",
"Value": 4
}
]
}
}
]
Overall pretty simple, yet powerful!