带有嵌套JSON对象的模型绑定



我正在编写一个端点来接受来自第三方的webhook上的POST请求,并且他们发送的数据是JSON编码的主体。因此,我无法控制发送给我的数据,我需要处理它。我的问题是,他们在JSON中进行了大量嵌套,由于我只使用了他们发送给我一些密钥,我不想为了获得我想要的数据而创建一堆不必要的嵌套模型。下面是一个有效载荷示例:

{
id: "123456",
user: {
"name": {
"first": "John",
"Last": "Doe"
}
},
"payment": {
"type": "cash"
}
}

我想把它放在一个模型中,看起来像:

public class SalesRecord
{
public string FirstName {get; set;}
public string LastName {get; set;}
public string PaymentType {get; set;}
}

端点示例(还不多(:

[HttpPost("create", Name = "CreateSalesRecord")]
public ActionResult Create([FromBody] SalesRecord record)
{
return Ok(record);
}

我过去的工作是在Phalcon PHP框架中进行的,在那里我通常只直接访问POST Body并自己设置模型中的值。我当然看到了模型绑定的优点,但我还不知道如何正确处理这种情况。

对于这样的场景,需要一个自定义的模型绑定器。该框架允许这种灵活性。

使用此处提供的漫游

自定义模型活页夹样品

并使其适应这个问题。

以下示例在SalesRecord模型上使用ModelBinder属性:

[ModelBinder(BinderType = typeof(SalesRecordBinder))]
[JsonConverter(typeof(JsonPathConverter))]
public class SalesRecord {
[JsonProperty("user.name.first")]
public string FirstName {get; set;}
[JsonProperty("user.name.last")]
public string LastName {get; set;}
[JsonProperty("payment.type")]
public string PaymentType {get; set;}
}

在前面的代码中,ModelBinder属性指定了应用于绑定SalesRecord操作参数的IModelBinder的类型。

SalesRecordBinder用于绑定SalesRecord参数,方法是尝试使用自定义JSON转换器解析发布的内容,以简化反盗版。

class JsonPathConverter : JsonConverter {
public override object ReadJson(JsonReader reader, Type objectType, 
object existingValue, JsonSerializer serializer) {
JObject jo = JObject.Load(reader);
object targetObj = Activator.CreateInstance(objectType);
foreach (PropertyInfo prop in objectType.GetProperties()
.Where(p => p.CanRead && p.CanWrite)) {
JsonPropertyAttribute att = prop.GetCustomAttributes(true)
.OfType<JsonPropertyAttribute>()
.FirstOrDefault();
string jsonPath = (att != null ? att.PropertyName : prop.Name);
JToken token = jo.SelectToken(jsonPath);
if (token != null && token.Type != JTokenType.Null) {
object value = token.ToObject(prop.PropertyType, serializer);
prop.SetValue(targetObj, value, null);
}
}
return targetObj;
}
public override bool CanConvert(Type objectType) {
// CanConvert is not called when [JsonConverter] attribute is used
return false;
}
public override bool CanWrite {
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, 
JsonSerializer serializer) {
throw new NotImplementedException();
}
}

来源:我可以在属性中指定一个路径,将类中的属性映射到JSON中的子属性吗?

public class SalesRecordBinder : IModelBinder {
public Task BindModelAsync(ModelBindingContext bindingContext) {
if (bindingContext == null){
throw new ArgumentNullException(nameof(bindingContext));
}
// Try to fetch the value of the argument by name
var valueProviderResult = bindingContext.ValueProvider
.GetValue(bindingContext.ModelName);
if (valueProviderResult == ValueProviderResult.None){
return Task.CompletedTask;
}
var json = valueProviderResult.FirstValue;
// Check if the argument value is null or empty
if (string.IsNullOrEmpty(json)) {
return Task.CompletedTask;
}
//Try to parse the provided value into the desired model
var model = JsonConvert.DeserializeObject<SalesRecord>(json);
//Model will be null if unable to desrialize.
if (model == null) {
bindingContext.ModelState
.TryAddModelError(
bindingContext.ModelName,
"Invalid data"
);
return Task.CompletedTask;
}
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, model);
//could consider checking model state if so desired.
//set result state of binding the model
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}

从那里开始,现在应该是一个简单的问题,在行动中使用模型

[HttpPost("create", Name = "CreateSalesRecord")]
public IActionResult Create([FromBody] SalesRecord record) {
if(ModelState.IsValid) {
//...
return Ok();
}
return BadRequest(ModelState);
}

免责声明:这还没有经过测试。根据以上提供的链接来源,可能仍有问题需要解决

注意:这假设JSON输入始终有效。如果这不是真的,你将不得不添加一些检查。

如果你不想让它变得太复杂,你可以使用DLR的帮助。NewtonSoft.Json序列化程序允许您将其反序列化为dynamic对象:

[HttpPost]
public IActionResult CreateSalesRecord([FromBody]dynamic salesRecord)
{
return Ok(new SalesRecord
{
FirstName = salesRecord.user.name.first,
LastName = salesRecord.user.name.Last,
PaymentType = salesRecord.payment.type
});
}
[HttpPost]
public IActionResult Json(string json)
{
JObject j = JObject.Parse(json);
MyModel m = j.ToObject<MyModel>();
return View();
}

如果你的Json是字符串格式的,你可以试试这个。我相信要想让它发挥作用,你的数据模型必须是Json的精确表示。