如何区分 Asp.Net 核心模型绑定中 JSON 中的空数据和不存在数据?
How to differentiate between null and non existing data in JSON in Asp.Net Core model binding?
我想在 Asp.Net 核心的一个动作中区分这两个 json 输入:
{
"field1": null,
"field2": null
}
和
{
"field1": null,
}
我在 C# 中有一个像这样的普通 class:
public class MyData
{
public string Field1 { get; set;}
public string Field2 { get; set;}
}
我想 运行 可以接受 null 作为值的对象的部分更新,但是当该字段不在输入中时,这意味着我根本不想更新该字段(将其设置为 null 的其他原因)。
低调的会是这样:
public class MyData
{
public string Field1 { get; set; }
public string Field2 { get; set; }
// this can be extension method also.
public bool HasProperty(string propertyName)
{
return GetType().GetProperty(propertyName) != null;
}
}
主要内容:
string json = "{ \"field1\": null, \"field2\": null }";
MyData jsonObject = null;
jsonObject = JsonConvert.DeserializeObject<MyData>(json);
MyData source= null; // this will contain values
if (jsonObject.HasProperty("Field1"))
source.Field1 = jsonObject.Field1;
参考:Check if a property exist in a class
简介: Asp.net core获取你的request body然后反序列化为一个MyData类型的对象,然后通过传入对象调用你controller中的方法作为参数。从对象 myData 中,您无法知道 field2 是否为空或未通过。 属性 的两种方式都将具有空值。您试图查找的信息在反序列化时丢失了。
解决方法: 这个需要阅读请求体,检查请求体是否传递了该字段。在 asp.net 核心中,一旦请求主体被读取(由 asp.net 核心框架创建 MyData 对象),读取请求主体有点复杂。我们需要倒回请求流,然后读取它。它的代码如下。
[HttpPost]
public void Post([FromBody] MyData myData)
{
HttpContext.Request.Body.Seek(0, System.IO.SeekOrigin.Begin);
System.IO.StreamReader sr = new System.IO.StreamReader(HttpContext.Request.Body);
var requestBody = sr.ReadToEnd();
//Now check the requestBody if the field was passed using JSON parsing or string manipulation
Console.WriteLine(requestBody);
}
警告:虽然这会起作用。您尝试做的事情会降低可读性并使其他开发人员难以理解。区分字段值是否为空或不存在于请求正文中不是一种常见做法。
这就是我最终所做的,因为所有其他选项似乎都过于复杂(例如 jsonpatch、模型绑定)或者不会提供我想要的灵活性。
这个解决方案意味着要为每个 属性 编写一些样板文件,但不要太多:
public class UpdateRequest : PatchRequest
{
[MaxLength(80)]
[NotNullOrWhiteSpaceIfSet]
public string Name
{
get => _name;
set { _name = value; SetHasProperty(nameof(Name)); }
}
}
public abstract class PatchRequest
{
private readonly HashSet<string> _properties = new HashSet<string>();
public bool HasProperty(string propertyName) => _properties.Contains(propertyName);
protected void SetHasProperty(string propertyName) => _properties.Add(propertyName);
}
然后可以这样读取值:
if (request.HasProperty(nameof(request.Name)) { /* do something with request.Name */ }
这是使用自定义属性对其进行验证的方式:
var patchRequest = (PatchRequest) validationContext.ObjectInstance;
if (patchRequest.HasProperty(validationContext.MemberName) {/* do validation*/}
再多加 2 美分,我们采用与 类似的方式,只是我们不是从 setter 调用 SetHasProperty
,而是覆盖 DefaultContractResolver
]:
public class PatchRequestContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var prop = base.CreateProperty(member, memberSerialization);
prop.SetIsSpecified += (o, o1) =>
{
if (o is PatchRequest patchRequest)
{
patchRequest.SetHasProperty(prop.PropertyName);
}
};
return prop;
}
}
然后在启动中注册这个解析器:
services
.AddControllers()
.AddNewtonsoftJson(settings =>
settings.SerializerSettings.ContractResolver = new PatchRequestContractResolver());```
请注意,我们仍在使用 JSON.Net 而不是 System.Text.Json(.Net 3+ 的默认设置)进行反序列化。截至目前 there's no way 使用 System.Text.Json
做类似于 DefaultContractResolver
的事情
我想在 Asp.Net 核心的一个动作中区分这两个 json 输入:
{
"field1": null,
"field2": null
}
和
{
"field1": null,
}
我在 C# 中有一个像这样的普通 class:
public class MyData
{
public string Field1 { get; set;}
public string Field2 { get; set;}
}
我想 运行 可以接受 null 作为值的对象的部分更新,但是当该字段不在输入中时,这意味着我根本不想更新该字段(将其设置为 null 的其他原因)。
低调的会是这样:
public class MyData
{
public string Field1 { get; set; }
public string Field2 { get; set; }
// this can be extension method also.
public bool HasProperty(string propertyName)
{
return GetType().GetProperty(propertyName) != null;
}
}
主要内容:
string json = "{ \"field1\": null, \"field2\": null }";
MyData jsonObject = null;
jsonObject = JsonConvert.DeserializeObject<MyData>(json);
MyData source= null; // this will contain values
if (jsonObject.HasProperty("Field1"))
source.Field1 = jsonObject.Field1;
参考:Check if a property exist in a class
简介: Asp.net core获取你的request body然后反序列化为一个MyData类型的对象,然后通过传入对象调用你controller中的方法作为参数。从对象 myData 中,您无法知道 field2 是否为空或未通过。 属性 的两种方式都将具有空值。您试图查找的信息在反序列化时丢失了。
解决方法: 这个需要阅读请求体,检查请求体是否传递了该字段。在 asp.net 核心中,一旦请求主体被读取(由 asp.net 核心框架创建 MyData 对象),读取请求主体有点复杂。我们需要倒回请求流,然后读取它。它的代码如下。
[HttpPost]
public void Post([FromBody] MyData myData)
{
HttpContext.Request.Body.Seek(0, System.IO.SeekOrigin.Begin);
System.IO.StreamReader sr = new System.IO.StreamReader(HttpContext.Request.Body);
var requestBody = sr.ReadToEnd();
//Now check the requestBody if the field was passed using JSON parsing or string manipulation
Console.WriteLine(requestBody);
}
警告:虽然这会起作用。您尝试做的事情会降低可读性并使其他开发人员难以理解。区分字段值是否为空或不存在于请求正文中不是一种常见做法。
这就是我最终所做的,因为所有其他选项似乎都过于复杂(例如 jsonpatch、模型绑定)或者不会提供我想要的灵活性。
这个解决方案意味着要为每个 属性 编写一些样板文件,但不要太多:
public class UpdateRequest : PatchRequest
{
[MaxLength(80)]
[NotNullOrWhiteSpaceIfSet]
public string Name
{
get => _name;
set { _name = value; SetHasProperty(nameof(Name)); }
}
}
public abstract class PatchRequest
{
private readonly HashSet<string> _properties = new HashSet<string>();
public bool HasProperty(string propertyName) => _properties.Contains(propertyName);
protected void SetHasProperty(string propertyName) => _properties.Add(propertyName);
}
然后可以这样读取值:
if (request.HasProperty(nameof(request.Name)) { /* do something with request.Name */ }
这是使用自定义属性对其进行验证的方式:
var patchRequest = (PatchRequest) validationContext.ObjectInstance;
if (patchRequest.HasProperty(validationContext.MemberName) {/* do validation*/}
再多加 2 美分,我们采用与 SetHasProperty
,而是覆盖 DefaultContractResolver
]:
public class PatchRequestContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var prop = base.CreateProperty(member, memberSerialization);
prop.SetIsSpecified += (o, o1) =>
{
if (o is PatchRequest patchRequest)
{
patchRequest.SetHasProperty(prop.PropertyName);
}
};
return prop;
}
}
然后在启动中注册这个解析器:
services
.AddControllers()
.AddNewtonsoftJson(settings =>
settings.SerializerSettings.ContractResolver = new PatchRequestContractResolver());```
请注意,我们仍在使用 JSON.Net 而不是 System.Text.Json(.Net 3+ 的默认设置)进行反序列化。截至目前 there's no way 使用 System.Text.Json
DefaultContractResolver
的事情