将 Json.NET 用于 JSON 模型绑定

Using Json.NET for JSON Model Binding

我有一个方法通过 AJAX 发布到 header:

public JsonResult GetDocuments(string searchTerm, SortRequest sort)

SortRequestobject定义如下:

[DataContract]
public class SortRequest
{
    [DataMember(Name = "field")]
    public string Field { get; set; }

    [DataMember(Name = "dir")]
    public string Direction { get; set; }
}

由于遗留代码,JSON object 的 属性 名称 "dir" 与 C# 属性 名称不直接匹配。我们想使用 Json.NET 作为 JSON 请求的模型绑定器,因为它能够处理这个,但问题是进入模型绑定器的 JSON 看起来像一个单一的 object 有两个顶级属性,"searchTerm" 和 "sort"。反序列化过程然后尝试将整个 JSON 字符串映射到每个明显失败的方法参数中。

我已经尝试查看现在开源的 .NET MVC 代码,但还不能确定 DefaultModelBinder class 如何优雅地处理这个问题。到目前为止,我能看到的唯一选择是将每个 JSON 操作转换为接受单个请求参数,但这似乎不是一个好的解决方案,因为 DefaultModelBinder 不需要这个。

编辑澄清:

JSON 请求字符串如下所示:

{
    "searchTerm": "test",
    "sort": {
        "field": "name",
        "dir": "asc"
    }
}

我们正在覆盖 DefaultModelBinder,并且仅在请求类型为 application/json 时使用 Json.NET。这是相关代码:

var request = controllerContext.HttpContext.Request;

request.InputStream.Seek(0, SeekOrigin.Begin);

using (var reader = new StreamReader(request.InputStream))
{
    var jsonString = reader.ReadToEnd();

    result = JsonConvert.DeserializeObject(jsonString, bindingContext.ModelType);
}

方法中每个参数的bindingContext.ModelType都会设置成String和SortRequest,但是由于上面是单个JSONobject,所以不映射对于这些类型中的任何一种,因此在方法本身内部,所有内容都设置为默认值。

我认为 JsonProperty 属性可用于此,如下所示:

[DataContract]
public class SortRequest
{
    [DataMember(Name = "field")]
    [JsonProperty("field")]
    public string Field { get; set; }

    [DataMember(Name = "dir")]
    [JsonProperty("dir")]
    public string Direction { get; set; }
}

更新

在json的基础上添加绑定前缀:

public JsonResult GetDocuments(string searchTerm, [Bind(Prefix="sort"] SortRequest sort)

我最终使用 Json.NET 库中的 JToken.Parse 方法找到了解决方案。本质上发生的事情是我们检查 JSON 对象的顶级属性,看看是否存在我们试图绑定到的当前操作参数。如果动作的参数名称与传入的单个请求的 属性 名称之间存在重叠,则出现这种情况。我认为这足以让滑动成为边缘情况,因为它只需要一个将单个对象传递到需要多个对象的操作中。

这里是修改后的 BindModel 方法:

public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
    object result;

    if (IsJSONRequest(controllerContext))
    {
        var request = controllerContext.HttpContext.Request;

        request.InputStream.Seek(0, SeekOrigin.Begin);

        using (var reader = new StreamReader(request.InputStream))
        {
            var jsonString = reader.ReadToEnd();

            // Only parse non-empty requests.
            if (!String.IsNullOrWhiteSpace(jsonString))
            {
                // Parse the JSON into a generic key/value pair object.
                var obj = JToken.Parse(jsonString);

                // If the string parsed and there is a top level property of the same
                // name as the parameter name we are looking for, use that property
                // as the JSON object to de-serialize.
                if (obj != null && obj.HasValues && obj[bindingContext.ModelName] != null)
                {
                    jsonString = obj[bindingContext.ModelName].ToString();
                }
            }

            result = JsonConvert.DeserializeObject(jsonString, bindingContext.ModelType);
        }
    }
    else
    {
        result = base.BindModel(controllerContext, bindingContext);
    }

    return result;
}