JSON Dates are Different in ASP.NET MVC and Web API

With the advent of ASP.NET MVC and Web API, working over plain HTML as opposed to obtrusive server side ‘web-controls’ has become the norm. Also partial post-backs and AJAX is a must. Among this paradigm shift from WebForms, JSON data transfer has gained tremendous popularity.

We all love JSON it’s easy to understand, light weight and works… almost! We all HATE JSON Dates. Why? Because JSON spec doesn’t say how to handle Dates. This minor detailing has resulted in a lot of head-scratching, a lots of hacks and lot of posts all across the web. I decided to write up a ‘Latest update’ on the JSON Dates Saga. Remember I am using Web API 4.0.2 which is the latest release version.

A special note of thanks to Sumit  Maitra who cracked the JSON serializer difference in MVC 4 and Web API.


JSON serializer used by MVC 4 and Web API are different

The JSON serializer used by ASP.NET MVC 4 and Web API 4 are different. MVC uses JsonDataContractSerializer whereas Web API uses the NewtonSoft Json Serializer. What’s the difference you ask? Here is a simple example

json-date-incorrect-in-mvc

When I click on ‘Get Date’ for the two views, one calls an MVC Controller and the other calls a Web API controller. You can see the difference in Date Formats.

The issue is well known and the solution is a simple single line of JavaScript that formats the date correctly.

value = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1")));

The JavaScript works well, as well as we are doing read-only stuff. As we can see below.

json-date-fixed-in-dom

I’ll come to the exact details a little later, but the fact is we are using Knockout Binding behind the scenes.

Remember I said the JavaScript works only for ‘Read Only’ conditions. Why?

The ‘Set Date’ button posts the data back on the screen to the respective controllers. Let’s post back without making any changes to the Date text boxes

For the MVC Controller, we get this

json-date-posted-incorrectly

As we can see, even though the JavaScript fixed the issue on screen (DOM), in the View Model things are still messed up.

On the other hand, for the Web Api Controller, we get the following. Essentially the date gets passed back correctly.

json-date-posted-correctly-api

Well now that we’ve established the different behavior, let’s try to smoothen things out. Also no one really uses a textbox for Date entry, so let’s jazz it up with a jQuery DatePicker widget.

jQuery Date Picker and KO Date Binding

Attaching Date Picker is easy, we just use the following script

$("#theData").datepicker();

We do it in both the sample-45.js and sample-api-45.js of this project.

jqueryui-date-picker

Let’s set the dates and Post back from both. Remember for the ASP.NET MVC sample, we are using a Knockout Binding Handler and for the Web API, we are using simple Text binding.

When we select the Date as April 19 and post the MVC Controller, we get this

still-incorrect-date-posted

So it’s still broken!

The Web Api Controller on the other hand works just fine.

correct-date-in-api-controller

Fixing the Date binding in Knockout for MVC

So far I’ve been talking about a ‘mysterious’ way to fix the date issue and we saw we were able to get rid of the ugly “/Date(…)/” formatting issue. But as we saw, it was a read-only solution. Posting, the data is still broken. So let’s take a deeper look at the solution.

Knockout as we know supports custom bindings so we fix the Date issue for MVC using a custom Date binding implementation as below. The init method initializes the bound field. It hooks on to the DatePicker’s change event and gets the updated date and puts it in the DOM on change of the Date Picker.

The update method on the other hand is called every time the observable changes. Here we check if the Date is the weird string that ASP.NET sends.

Now comes the crux of the ‘fix’, note the last two lines, we have a valid date in the ‘value’ variable. We take this value and put back into the KO View Model in the following two lines. Thanks again Sumit!

var vmVal = valueAccessor();
vmVal(new Date(value));
The complete DataBinding handler is as follows:
ko.bindingHandlers.date = {
init: function (element, valueAccessor, allBindingsAccessor)
{
  var dateField = valueAccessor();
  if (dateField() == "")
  {
   //initialize datepicker with some optional options
   var options = allBindingsAccessor().datepickerOptions || {};
   $(element).datepicker(options);
   //handle the field changing
   ko.utils.registerEventHandler(element, "change", function ()
   {
    var observable = valueAccessor();
    observable($(element).val());
    if (observable)
    {
     observable($(element).datepicker("getDate"));
     $(element).blur();
    }
   });
   //handle disposal (if KO removes by the template binding)
   ko.utils.domNodeDisposal.addDisposeCallback(element, function ()
   {
    $(element).datepicker("destroy");
   });
  }
},
update: function (element, valueAccessor)
{
  var value = ko.utils.unwrapObservable(valueAccessor());
  //handle date data coming via json from Microsoft
  if (String(value).indexOf('/Date(') == 0)
  {
   value = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1")));
  }
  current = $(element).datepicker("getDate");
  if (value - current !== 0)
  {
   $(element).datepicker("setDate", value);
  }
  // Put Data back into the KO View Model
  var vmVal = valueAccessor();
  vmVal(new Date(value));
}
};

Now if we post back to the server, we see the data is posted correctly now (albeit we have ignored the Time here, but that’s for another day).

finally-data-posted-correctly

Conclusion

Thanks to different Json Serializers, Dates are serialized differently in ASP.NET MVC and Web API. It can be mitigated in multiple ways, but when using Knockout for Data Binding, things can get a little hairy with two way data-binding. We saw one way to fix the issue using the custom Knockout.js binding. The code inspiration comes from this SO Post, which I adapted for two-way binding above.

Download the entire source code here (Github)

2 comments:

  1. Dates between server and client are indeed painful. While Web API allows you to change json serialization, MVC makes it harder.
    You need to extend JsonResult and override the ExecuteResult, then you can change the date format to to correct notation.
    JsonNetResult gist

    ReplyDelete
  2. You should be able to change the serializer to use Json.NET.

    You can also add a handler for date to use Moment.JS.

    ReplyDelete