ASP.NET Core is using non-default constructor if no public default constructor found

对着背影说爱祢 提交于 2019-12-08 19:29:25

Thanks to Panagiotis Kanavos for pointing out that Json.NET serializer is used in ASP.NET Core.
This led me to ConstructorHandling setting in the Json.NET documentation.

Reason of the behaviour

The documentation specifies the following:

ConstructionHandling.Default. First attempt to use the public default constructor, then fall back to a single parameterized constructor, then to the non-public default constructor.

That is Json.NET searches for the constructors in the following order:

  • Public default constructor
  • Public parameterized constructor
  • Private default constructor

And this is the reason why parameterized constructor is preferred over private default constructor.

Use private default ctor instead of public parameterized ctor (one class)

JsonConstructorAttribute can be used to explicitly specify constructor to Json.NET deserializer:

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

Now Json.NET deserializer will use the explicitly specified constructor.

Use private default ctor instead of public parameterized ctor (service)

Another way is to change ConstructionHandling of JsonSerializerSettings property to use AllowNonPublicDefaultConstructor:

ConstructionHandling.AllowNonPublicDefaultConstructor: Json.NET will use a non-public default constructor before falling back to a parameterized constructor.

This is how it can be done in the Startup.cs:

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

This will apply this logic to all models and the deserializer will always prefer private default constructor over public parameterized constructors for all models.

Parameterized constructor in request model can be a code smell

In this particular example, the code has been presented to reproduce the problem.

In real code, parameterized or multiple constructors can imply that you are using classes for multiple purposes, i.e. domain model and request model. This can eventually lead to problems with reusing or supporting this code.

DTOs with public default constructor and no logic should be used for requests to avoid these problems.

What you want is not logical. private members, including constructor are not accessible from outside.

If a class has one or more private constructors and no public constructors, other classes (except nested classes) cannot create instances of this class.

To bind model, controller has only one way - call public c-tor with every argument set to null.

To make model binding possible, the class must have a public default constructor and public writable properties to bind. When model binding occurs, the class is instantiated using the public default constructor, then the properties can be set.

So to bind successfully you should:

  1. Have public default c-tor
  2. Have public setters for properties.
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!