Monday, November 21, 2011

Serializing dates in MVC

Update: I've rewritten the code for simplicity and efficiency. You can check it out here.
Ah, dates. Simple little things, really, especially when working with .Net and JavaScript, right?
Well, sort of. See, Microsoft decided that when serializing DateTime objects, they should be formatted like this:
"\/Date(12345678)\/". Unfortunately, without running eval() on that, that format is useless.
I've never really had this issue before, because when working with dates on the client side, I always use strings.
So why the issue now?
Because, along with using MVC, I decided to use the Entity Framework, which made it really easy to return a bunch of objects from the database:
return Json(db.Tasks.ToList());
In the past, this would have been more like:

DataTable dt = new DataTable();
dt.Load(command.ExecuteReader());
return dt.Rows.Cast<DataRow>().Select(
                r =>
                new
                    {
                        id = (int) r["id"],
                        name = (string) r["name"],
                        date = ((DateTime) r["date"]).ToString("yyyy-MM-dd")
                    });

Now, I could simply go on using the select statement, but I just couldn't bear the thought of doing that everytime I had to work with dates (since that is all the time). So instead I wrote a custom Json serializer derived from JsonResult. I've uploaded the source code (link at bottom), but the real magic is in these lines:

public bool HasDates { get; set; }

public override void ExecuteResult(ControllerContext context)
{ 
    //... a bunch of code ...
    if (Data != null)
    {
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        response.Write(HasDates ? FixDates(serializer.Serialize(Data)) : serializer.Serialize(Data));
    }
}   

public string FixDates(string data)
{
    var matches = Regex.Matches(data, @"\\/Date\((?<ticks>\d+)?\)\\/").Cast<Match>().ToArray();
    for (int i = matches.Length - 1; i >= 0; i--)
    {
        var match = matches[i];
        data = data.Remove(match.Index, match.Length)
            .Insert(match.Index,
                    new DateTime(1970, 1, 1).AddMilliseconds(long.Parse(match.Groups["ticks"].Value))
                        .ToString("yyyy-MM-dd HH:mm:ss"));
    }
    return data;
}

The code simply loops through the standard serialized result and replaces any MS-formatted date strings with my kind of date string (yyyy-MM-dd HH:mm:ss eg. "2011-11-21 19:48:13").

Updated return call:
return new ProperJsonResult() { HasDates = true, Data = db.Tasks.ToList() };

Click here to download ProperJsonResult.cs

No comments:

Post a Comment