14

In MVC3, is it possible to automatically bind javascript objects to models if the model has nested objects? My model looks like this:

 public class Tweet
 {
    public Tweet()
    {
         Coordinates = new Geo();
    }
    public string Id { get; set; }
    public string User { get; set; }
    public DateTime Created { get; set; }
    public string Text { get; set; }
    public Geo Coordinates { get; set; } 

}

public class Geo {

    public Geo(){}

    public Geo(double? lat, double? lng)
    {
        this.Latitude = lat;
        this.Longitude = lng;
    }

    public double? Latitude { get; set; }
    public double? Longitude { get; set; }

    public bool HasValue
    {
        get
        {
            return (Latitude != null || Longitude != null);
        }
    }
}

When I post the following JSON to my controller everything except "Coordinates" binds successfully:

{"Text":"test","Id":"testid","User":"testuser","Created":"","Coordinates":{"Latitude":57.69679752892457,"Longitude":11.982091465576104}}

This is what my controller action looks like:

    [HttpPost]
    public JsonResult ReTweet(Tweet tweet)
    {
        //do some stuff
    }

Am I missing something here or does the new auto-binding feature only support primitive objects?

jul
  • 1,054
  • 2
  • 11
  • 24

2 Answers2

19

Yes, you can bind complex json objects with ASP.NET MVC3.

Phil Haack wrote about it recently.
You've got a problem with your Geo class here.
Don't use nullable properties:

public class Geo
{

    public Geo() { }

    public Geo(double lat, double lng)
    {
        this.Latitude = lat;
        this.Longitude = lng;
    }

    public double Latitude { get; set; }
    public double Longitude { get; set; }

    public bool HasValue
    {
        get
        {
            return (Latitude != null || Longitude != null);
        }
    }
}

This is the javascript code I've use to test it:

var jsonData = { "Text": "test", "Id": "testid", "User": "testuser", "Created": "", "Coordinates": { "Latitude": 57.69679752892457, "Longitude": 11.982091465576104} };
var tweet = JSON.stringify(jsonData);
$.ajax({
    type: 'POST',
    url: 'Home/Index',
    data: tweet,
    success: function () {
        alert("Ok");
    },
    dataType: 'json',
    contentType: 'application/json; charset=utf-8'
});

UPDATE

I've tried to do some experiments with model binders and I came out with this solutions which seems to work properly with nullable types.

I've created a custom model binder:

using System;
using System.Web.Mvc;
using System.IO;
using System.Web.Script.Serialization;

public class TweetModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var contentType = controllerContext.HttpContext.Request.ContentType;
        if (!contentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
            return (null);

        string bodyText;

        using (var stream = controllerContext.HttpContext.Request.InputStream)
        {
            stream.Seek(0, SeekOrigin.Begin);
            using (var reader = new StreamReader(stream))
                bodyText = reader.ReadToEnd();
        }

        if (string.IsNullOrEmpty(bodyText)) return (null);

        var tweet = new JavaScriptSerializer().Deserialize<Models.Tweet>(bodyText);

        return (tweet);
    }
}

and I've registered it for all types tweet:

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        ModelBinders.Binders.Add(typeof(Models.Tweet), new TweetModelBinder());

        RegisterGlobalFilters(GlobalFilters.Filters);
        RegisterRoutes(RouteTable.Routes);
    }
LeftyX
  • 35,328
  • 21
  • 132
  • 193
  • Removing nullable does indeed work but messes up the model since sending blank values will result in 0 which is a valid lat/lng value. Guess the only way is to manually deserialize my json. – jul Jul 03 '11 at 22:31
  • I just noticed that if I put the values in quotes it binds fine with nullable properties. Surely this must be a bug? – jul Jul 03 '11 at 22:42
  • @jul: you're right.I did some experiments trying to bind your action using a custom model binder (ActionResult InsertTweet([ModelBinder(typeof(TweetModelBinder))] Models.Tweet tweet)) and the values are there, in the DictionaryValueProvider. – LeftyX Jul 04 '11 at 11:06
  • 1
    @jul: I've updated my answer with a new solutions. It works properly this time. Hope it helps. – LeftyX Jul 10 '11 at 12:36
0

I experienced the same issue, however in my case this was related to how the data was being transmitted from client side. Make sure the AJAX request is using the proper headers and formatting. For example:

    dataType: 'json',
    contentType: 'application/json; charset=UTF-8',
    data: JSON.stringify({
        MemberId : '123',
        UserName: '456',
        Parameters: [
            { Value : 'testing' },
            { Value : 'test2' }
        ]
    }),