【发布时间】:2022-07-23 11:15:27
【问题描述】:
我的场景是这样的:
我正在 REST API 中实现更新操作,该 API 处理典型的 CRUD 类型操作并使用 ASP.NET Core 开发。 API 中的操作通常采用 json 负载。
我希望能够区分何时从 json 有效负载中省略某个属性,以及何时将 json 有效负载中的 a 属性设置为 null(或该特定类型的默认值)。
例如,在一个特定的操作中,有一个可选的到期日期,它可以是 DateTime 值或 null。 因此,一旦模型被绑定 - 并且到期日期为空 - 如何判断它是否为空,因为客户端想要将其更新为空(设置为无到期日期),或者因为客户端从有效负载中省略了它(并且所以会表明他们根本不想更新它)?
我尝试过的:
我在代码分析命名空间中实现了一个类似于Optional 的结构,因为它具有对一个值的引用,并跟踪该值是否已设置/是否有意义。
我尝试为我的结构实现自定义 JSON 反序列化器和 TypeConverter,但两种方法似乎都不起作用。令人沮丧的是,如果属性的值为 null,在模型绑定期间似乎没有调用 TypeConverter 的自定义反序列化器或 ConvertFrom 方法,这会导致我的可选项的默认构造函数用于未省略的 null 值,所以我无法区分空值和遗漏。
我拥有的可选值是模型上的属性(我想在多个模型中使用它们),所以从我收集到的信息来看,使用自定义模型绑定器是不合适的(并且并没有真正让我得到任何我不认为更接近)。
举个具体的例子,我的结构体的精简版如下;
[TypeConverter(typeof(OptionalConverter))]
[JsonConverter(typeof(OptionalJsonConverter))]
public readonly struct Optional<T>
{
private readonly T _value;
public T Value
{
get
{
return _value;
}
}
public bool HasValue { get; }
public Optional(T value)
{
HasValue = true;
_value = value;
}
public static implicit operator Optional<T>(T value)
{
return new Optional<T>(value);
}
public static explicit operator T(Optional<T> value)
{
return value.Value;
}
}
相关的类型转换器方法如下:
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return true;
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return Activator.CreateInstance(typeof(Optional<>).MakeGenericType(context.PropertyDescriptor.PropertyType), new object[] { value });
}
我的相关 JSON 转换器方法是(我使用的是 newtonsoft(请注意,如果我手动反序列化字符串,它会按照我想要的方式工作)):
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.Undefined)
{
var genericTypes = objectType.GetGenericArguments();
var value = serializer.Deserialize(reader, genericTypes[0]);
return Activator.CreateInstance(
typeof(Optional<>).MakeGenericType(genericTypes[0]),
new object[] { value });
}
return existingValue;
}
我有如下测试路线;
[HttpPut]
[Route("foo")]
public IActionResult Foo(SomeDto someDto)
{
return Ok();
}
还有一个测试模型DTO;
public class SomeDto
{
public Optional<string> Foo { get; set; }
public Optional<string> Bar { get; set; }
public Optional<string> Baz { get; set; }
}
鉴于{ "foo": "foo", "bar": null } 的 /foo 的 PUT,我希望将 someDto 的值绑定为:
{
Foo: { Value: "foo", HasValue: true },
Bar: { Value: null, HasValue: true }, <-- was passed as null.
Baz: { Value: null, HasValue: false } <-- omitted.
}
但是我得到了
{
Foo: { Value: "foo", HasValue: true },
Bar: { Value: null, HasValue: false }, <-- was passed as null.
Baz: { Value: null, HasValue: false } <-- omitted.
}
这似乎是因为一旦值为 null,ASP.NET 绑定器就会使用结构的默认构造函数,因此不会让您有机会使用 JSON 或类型转换器提供不同的值。 我对解决这个绑定问题可能缺少的东西感到茫然,但也许我缺少了一些东西。如果没有人确认这种方法不可行,至少会有所帮助。
注意:
我意识到还有其他方法可以实现类似的结果,例如有一个单独的路由来更新实体上的每个字段,或者使用 jsonpatch。但是这些对客户端如何使用 API 有影响,所以我宁愿只在无法解决的情况下走这条路。
【问题讨论】:
-
我认为您应该实现一个自定义模型绑定器,以便您可以自己绑定值并且可以区分默认空值或未发送值空值。希望对您有所帮助:docs.microsoft.com/en-us/aspnet/core/mvc/advanced/…
标签: asp.net asp.net-core binding deserialization model-binding