DataContract model binding to JSON in ASP.NET MVC Action Method Arguments
Asked Answered
M

3

7

MVC3 comes out of the box with JsonValueProviderFactory() which is very handy for binding incoming JSON to a model. Unfortunately, I can't figure out how to setup model contracts with names that differ from the incoming JSON. For example:

[DataContract(Name = "session")]
public class FacebookSession
{
    [DataMember(Name = "access_token")]
    public string AccessToken { get; set; }

    [DataMember(Name = "expires")]
    public int? Expires { get; set; }

    [DataMember(Name = "secret")]
    public string Secret { get; set; }

    [DataMember(Name = "session_key")]
    public string Sessionkey { get; set; }

    [DataMember(Name = "sig")]
    public string Signature { get; set; }

    [DataMember(Name = "uid")]
    public string UserId { get; set; }
}

when passing in a json object representing the facebook session, the properties secret and expires bind properly, but the rest do not because the property name is different than the json key name. I would expect that the datacontract serializer would try and bind to the name provided in the attribute, but that doesn't appear to be the case. Does anyone have any workaround suggestions?

Edit

An example of how I would use this model:

    public ActionResult Log(int? custId, FacebookSession response)
    {       
          ViewBag.Id = response.UserId;                         
          return View();
    }
Montherlant answered 8/4, 2011 at 19:13 Comment(0)
M
6

I ended up using gt124's link model binder example along with a better model binder to write my own model binding logic. It ended up looking like this:

public interface IFilteredModelBinder : IModelBinder
    {
        bool IsMatch(Type modelType);
    }

public class SmartModelBinder : DefaultModelBinder
{
    private readonly IFilteredModelBinder[] _filteredModelBinders;

    public SmartModelBinder(IFilteredModelBinder[] filteredModelBinders)
    {
        _filteredModelBinders = filteredModelBinders;
    }

    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        foreach (var filteredModelBinder in _filteredModelBinders)
        {
            if (filteredModelBinder.IsMatch(bindingContext.ModelType))
            {
                return filteredModelBinder.BindModel(controllerContext, bindingContext);
            }
        }

        return base.BindModel(controllerContext, bindingContext);
    }
}

public class NewtonsoftJsonModelBinder : IFilteredModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
        {
            // not JSON request
            return null;
        }

        var request = controllerContext.HttpContext.Request;
        request.InputStream.Position = 0;
        var incomingData = new StreamReader(request.InputStream).ReadToEnd();

        if (String.IsNullOrEmpty(incomingData))
        {
            // no JSON data
            return null;
        }
        object ret = JsonConvert.DeserializeObject(incomingData, bindingContext.ModelType); 
        return ret;
    }

    public bool IsMatch(Type modelType)
    {
        var ret = (typeof(JsonModel).IsAssignableFrom(modelType));
        return ret;
    }
}

I then used JSON.net attributes to map to the different object properties (instead of DataContracts) on the models. The models all inherited from an empty base class JsonModel.

Montherlant answered 11/4, 2011 at 17:28 Comment(1)
Make sure you replace the default binder: in Application_Start: ModelBinders.Binders.DefaultBinder = new SmartModelBinder(new [] {new NewtonsoftJsonModelBinder()});Joaquin
L
1

You can pass it in as a string and manually call the datacontractdeserializer, unless you write your own modelbinder. I believe the default binder uses the javascriptserializer, not the datacontractjsserializer.

Model Binder Example

Lit answered 8/4, 2011 at 19:21 Comment(2)
this doesn't work, response is = to null. I mean, I could probably do JSON.Stringify() in the js call, but that would make me sad.Montherlant
The example above might give you enough to write a modelbinder that uses the datacontractjsonserializer...Lit
W
0

You don't need to replace the default binder, just write an attribute like that

public class DataContractJsonModelBinderAttribute : CustomModelBinderAttribute
{
    public override IModelBinder GetBinder()
    {
        return new DataContractJsonModelBinder();
    }
}

the using is simple

[DataContract(Name = "session")]
[DataContractJsonModelBinder]
public class FacebookSession
{
    [DataMember(Name = "access_token")]
    public string AccessToken { get; set; }

    [DataMember(Name = "expires")]
    public int? Expires { get; set; }

    [DataMember(Name = "secret")]
    public string Secret { get; set; }

    [DataMember(Name = "session_key")]
    public string Sessionkey { get; set; }

    [DataMember(Name = "sig")]
    public string Signature { get; set; }

    [DataMember(Name = "uid")]
    public string UserId { get; set; }
}

UPDATE Now y can simply use built-in Json.NET functionality like that:

[JsonObject]
public class FacebookSession
{
    [JsonProperty("access_token")]
    public string AccessToken { get; set; }
}

and if necessary

var facebokSession = JsonConvert.DeserializeObject<FacebookSession>(facebookSessionJsonString);
Weaponless answered 9/6, 2012 at 10:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.