首页 > 解决方案 > 如果未找到公共默认构造函数,则 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是必需的。

有趣的是 - 如果我公开默认构造函数,那么它会按预期工作,但我不想这样做。

为什么它不使用私有构造函数,我怎样才能让它使用它而不将其标记为公共?

另一个问题是模型绑定器是否了解如何使用带有参数的构造函数使用反射?

标签: c#asp.net-coredata-annotationsmodel-binding

解决方案


感谢 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(服务)

另一种方法是更改​​要使用ConstructionHandlingJsonSerializerSettings属性AllowNonPublicDefaultConstructor

ConstructionHandling.AllowNonPublicDefaultConstructor: Json.NET 将在回退到参数化构造函数之前使用非公共默认构造函数。

这是如何在以下情况下完成的Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().AddJsonOptions(o => {
        o.SerializerSettings.ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor;
    });
}

这将将此逻辑应用于所有模型,并且反序列化器将始终更喜欢私有默认构造函数而不是所有模型的公共参数化构造函数。

请求模型中的参数化构造函数可能是代码异味

在此特定示例中,已提供代码以重现该问题。

在实际代码中,参数化或多个构造函数可能意味着您将类用于多种目的,即域模型和请求模型。这最终会导致重用或支持此代码的问题。

具有公共默认构造函数且没有逻辑的 DTO 应用于请求以避免这些问题。


推荐阅读