13

We know that MVC returns DateTime for JsonResult in this format: /Date(1240718400000)/, and we know how to parse it in JS.

However, It seems that MVC doesn't accept DateTime parameter being sent in this way. For example, I have the following Action.

[HttpGet]
public ViewResult Detail(BookDetail details) { //... }

The BookDetail class contains a DateTime field named CreateDate, and I passed a JSON object from JS in this format:

{"CreateDate": "/Date(1319144453250)/"}

CreateDate is recognized as null.

If I passed the JSON in this way, it works as expected:

{"CreateDate": "2011-10-10"}

The problem is that I cannot change client side code in an easy way, have to stick to /Date(1319144453250)/ this format. I have to make changes in server side.

How to solve this problem? Is that anything related to ModelBinder?

Thanks so much in advance!

tshao
  • 1,127
  • 2
  • 8
  • 23
  • One other easier solution is [here][1] [1]: http://stackoverflow.com/questions/12069171/pass-json-object-to-mvc-controller-as-an-argument/12085898#12085898 – Mohsen Afshin Aug 23 '12 at 20:06

3 Answers3

7

The problem, as you suspected, is a model binding issue.

To work around it, create a custom type, and let's call it JsonDateTime. Because DateTime is a struct, you cannot inherit from it, so create the following class:

public class JsonDateTime
{
    public JsonDateTime(DateTime dateTime)
    {
        _dateTime = dateTime;
    }

    private DateTime _dateTime;

    public DateTime Value
    {
        get { return _dateTime; }
        set { _dateTime = value; }
    }
}

Change CreateDate to this type. Next, we need a custom model binder, like so:

public class JsonDateTimeModelBinder : IModelBinder  
{ 
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
    { 
        var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ToString(); 
        return new DateTime(Int64.Parse(
            value.Substring(6).Replace(")/",String.Empty))); // "borrowed" from skolima's answer
    }
}

Then, in Global.asax.cs, in Application_Start, register your custom ModelBinder:

ModelBinders.Binders.Add(typeof(JsonDateTime), new JsonDateTimeModelBinder());
counsellorben
  • 10,924
  • 3
  • 40
  • 38
  • Thanks counsellorben (suppose this is your name:), i'll try it out. Can you tell me more about the ModelBinder, and maybe point out the relevant area in MVC's source code? – tshao Oct 21 '11 at 13:52
  • You should look at the DefaultModelBinder. Look at `DefaultModelBinder.cs` in the System.Web.Mvc project, in the MVC folder. – counsellorben Oct 21 '11 at 15:21
  • 2
    Why is the wrapper object JsonDateTime being created? You mention '...because DateTime is a struct, you cannot inherit from it', but I see no inheritance being used here. – Marchy Mar 28 '12 at 23:48
  • in MVC 4 I need to use ...bindingContext.ValueProvider.GetValue(bindingContext.ModelName).RawValue.ToString() ... – Dai Bok Sep 29 '15 at 14:52
2

I think using custom Model Binder will do the trick. The below model binder class will work on both cases. It will parse all dot net recognizable date string as well as JSON formatted date string. No need to change any existing code.

public class DateTimeModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var model = base.BindModel(controllerContext, bindingContext);
        if (model == null && (bindingContext.ModelType == typeof(DateTime) || bindingContext.ModelType == typeof(DateTime?)))
        {
            var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
            if (value == null || String.IsNullOrEmpty(value.AttemptedValue))
                model = (bindingContext.ModelType == typeof(DateTime?)) ? null : (object)DateTime.MinValue;
            else if (Regex.IsMatch(value.AttemptedValue, @"\/Date\(\d+\)\/"))
                model = new DateTime(1970, 1, 1).AddMilliseconds(Int64.Parse(value.AttemptedValue.Substring(6).Replace(")/", String.Empty))).ToLocalTime();
            //else //Any other format
        }
        return model;
    }
}

Configure Model Binder in Application_Start of Global.asax

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        //Your Existing Code....

        ModelBinders.Binders.Add(typeof(DateTime), new DateTimeModelBinder());
        ModelBinders.Binders.Add(typeof(DateTime?), new DateTimeModelBinder());
    }
}

Vote If it helps

2

In your model, use this to parse the date:

// property
String CreateDate;
DateTime CreateDateAsDate;

// drop prefix, drop suffix, parse as long and read as ticks
CreateDateAsDate date = new DateTime(Int64.Parse(
    CreateDate.Substring(6).Replace(")/",String.Empty)));
skolima
  • 31,963
  • 27
  • 115
  • 151
  • CreateDate is recognized as null, so this doesn't work. (Unless I define CreateDate as String, but I really don't want to do that...) – tshao Oct 21 '11 at 10:35
  • You'll have two properties, CreateDate as String and CreateDateAsDate with the actual parsed value. I'm afraid it won't work otherwise. – skolima Oct 21 '11 at 10:40
  • Seems like this is the only way...I'll wait a little bit longer for more replies ;-) – tshao Oct 21 '11 at 10:44