c# - 如果未找到公共默认构造函数,则 ASP.NET Core 使用非默认构造函数
问题描述
我正在编写托管在 Service Fabric 上的 ASP.NET Core 2.2.0 应用程序。
我有一个代表一个请求的类,并且我声明了两个构造函数:我自己使用的公共和序列化程序的私有:
public class MyClass
{
private MyClass() // for serializer
{
}
public MyClass(string myProperty) // for myself
{
MyProperty = myProperty ?? throw new ArgumentNullException(nameof(myProperty));
}
[Required]
public string MyProperty { get; private set; }
}
然后,我创建了一个 API 控制器:
[ApiController]
public class MyController
{
[HttpPut]
public async Task<IActionResult> Save([FromBody] MyClass model)
{
throw new NotImplementedException("Doesn't matter in this example");
}
}
我通过null
使用 Fiddler 调用值来测试它:
PUT /MyController (Content-Type: application/json)
{
"MyProperty": null
}
我遇到的问题是我的公共构造函数被调用时 myProperty 等于null
,这导致ArgumentNullException
被抛出并导致 500 Internal Server Error。
我期望的是它将使用私有无参数构造函数和私有设置器。然后,由于控制器被标记为ApiController
属性,该模型将根据数据注释自动验证,并导致 400 Bad Request,因为MyProperty
是必需的。
有趣的是 - 如果我公开默认构造函数,那么它会按预期工作,但我不想这样做。
为什么它不使用私有构造函数,我怎样才能让它使用它而不将其标记为公共?
另一个问题是模型绑定器是否了解如何使用带有参数的构造函数使用反射?
解决方案
感谢 Panagiotis Kanavos 指出 Json.NET 序列化程序用于 ASP.NET Core。
这导致我ConstructorHandling
在 Json.NET 文档中进行设置。
行为原因
该文档指定了以下内容:
构造处理。默认值。首先尝试使用公共默认构造函数,然后回退到单个参数化构造函数,然后再使用非公共默认构造函数。
即 Json.NET 按以下顺序搜索构造函数:
- 公共默认构造函数
- 公共参数化构造函数
- 私有默认构造函数
这就是为什么参数化构造函数优于私有默认构造函数的原因。
使用私有默认 ctor 而不是公共参数化 ctor(一类)
JsonConstructorAttribute
可用于显式指定 Json.NET 反序列化器的构造函数:
using Newtonsoft.Json;
public class MyClass
{
[JsonConstructor]
private MyClass() // for serializer
{
}
public MyClass(string myProperty) // for myself
{
MyProperty = myProperty ?? throw new ArgumentNullException(nameof(myProperty));
}
[Required]
public string MyProperty { get; private set; }
}
现在 Json.NET 反序列化器将使用显式指定的构造函数。
使用私有默认 ctor 而不是公共参数化 ctor(服务)
另一种方法是更改要使用ConstructionHandling
的JsonSerializerSettings
属性AllowNonPublicDefaultConstructor
:
ConstructionHandling.AllowNonPublicDefaultConstructor: Json.NET 将在回退到参数化构造函数之前使用非公共默认构造函数。
这是如何在以下情况下完成的Startup.cs
:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddJsonOptions(o => {
o.SerializerSettings.ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor;
});
}
这将将此逻辑应用于所有模型,并且反序列化器将始终更喜欢私有默认构造函数而不是所有模型的公共参数化构造函数。
请求模型中的参数化构造函数可能是代码异味
在此特定示例中,已提供代码以重现该问题。
在实际代码中,参数化或多个构造函数可能意味着您将类用于多种目的,即域模型和请求模型。这最终会导致重用或支持此代码的问题。
具有公共默认构造函数且没有逻辑的 DTO 应用于请求以避免这些问题。
推荐阅读
- dart - 如何在 Dart 中编码乌尔都语字符串?
- azure - 记住 Azure Active Directory Xamarin Forms 中上次登录的用户
- ios - 发布模式下的离子应用程序显示白屏
- bash - 如何测试 bats-assert 框架中是否设置了环境变量?
- azure - 将客户端应用程序添加到 Azure AD 应用程序会引发错误
- rust - 在 Rust 中,如何将发散函数作为参数传递给另一个函数
- node.js - 从 req.params.foo 查询参数到 mongoose find() 不起作用或返回空数组
- highcharts - 获取页面以匹配 highcharts 样式
- embedded-linux - imx6ul - 如何为 sdcard 图像选择正确的 dts?
- python - 如何为多个用户提供持续运行的 python 脚本(社交媒体机器人)