将 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;
}
我有一个方法通过 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;
}