如果未找到公共默认构造函数,则ASP.NET Core使用非默认构造函数

时间:2019-05-10 11:02:05

标签: c# asp.net-core data-annotations model-binding

我正在编写在Service Fabric上托管的ASP.NET Core 2.2.0应用程序。

我有一个代表请求的类,并且我声明了两个构造函数:public(供我自己使用)和private(供序列化器使用):

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");
    }
}

我通过用Fiddler调用null值来测试它:

PUT /MyController (Content-Type: application/json)
{
    "MyProperty": null
}

问题,我遇到的问题是调用myProperty等于null的公共构造函数,这导致抛出ArgumentNullException并导致500 Internal Server Error。

我期望的是它将使用私有的无参数构造函数和私有的setter方法。然后,由于控制器已标记有ApiController属性,因此该模型将根据数据注释自动进行验证,并且由于需要MyProperty,因此将导致400 Bad Request。

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

为什么不使用私有构造函数,如何在不将其标记为公共的情况下使用它?

另一个问题是模型联编程序是否了解如何通过反射使用带参数的构造函数?

2 个答案:

答案 0 :(得分:1)

感谢Panagiotis Kanavos指出在ASP.NET Core中使用了Json.NET序列化程序。
这导致我进入ConstructorHandling setting in the Json.NET documentation

行为原因

文档中指定了以下内容:

  

ConstructionHandling.Default。首先尝试使用公共默认构造函数,然后回退到单个参数化的构造函数,然后再使用非公共默认构造函数。

也就是说,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不应用于请求以避免这些问题。

答案 1 :(得分:-1)

您想要的是不合逻辑的。 private个成员(包括构造函数)不是accessible from outside

  

如果一个类具有一个或多个私有构造函数而没有公共构造函数,则其他类(嵌套类除外)不能创建该类的实例。

To bind model,控制器只有一种方法-将每个参数设置为null来调用public c-tor。

  

要使模型绑定成为可能,该类必须具有公共默认构造函数和公共可写属性进行绑定。发生模型绑定时,可以使用公共默认构造函数实例化该类,然后可以设置属性。

要成功绑定,您应该:

  1. 具有公开的默认c-tor
  2. 具有用于属性的公共设置器。