ASP.NET Core использует конструктор не по умолчанию, если не найден общедоступный конструктор по умолчанию - PullRequest
1 голос
/ 10 мая 2019

Я пишу приложение ASP.NET Core 2.2.0, размещенное в Service Fabric.

У меня есть класс, представляющий запрос, и я объявил два конструктора: открытый для моего собственного использования и закрытый для сериализаторов:

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 ошибочным запросам, поскольку требуется MyProperty.

Что интересно - если я сделаю конструктор по умолчанию общедоступным, то он будет работать, как и ожидалось, но я бы не хотел этого делать.

Почему он не использует приватный конструктор и как я могу сделать так, чтобы он использовал его, не помечая его как публичный?

Другой вопрос: понимает ли связыватель модели, как использовать конструктор с параметрами, использующими отражение?

Ответы [ 2 ]

1 голос
/ 10 мая 2019

Спасибо Панагиотису Канавосу за то, что он указал, что в ASP.NET Core используется сериализатор Json.NET.
Это привело меня к настройке ConstructorHandling в документации Json.NET .

Причина поведения

В документации указано следующее:

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 с открытым конструктором по умолчанию и без логики должны использоваться для запросов, чтобы избежать этих проблем.

0 голосов
/ 10 мая 2019

То, что вы хотите, не логично.private члены, включая конструктор, не доступны извне .

Если у класса есть один или несколько частных конструкторов и нет открытых конструкторов, другие классы (кроме вложенных классов) не могутсоздать экземпляры этого класса.

Чтобы связать модель , у контроллера есть только один способ - вызвать public c-tor с каждым аргументом, установленным в null.

Чтобы сделать возможной привязку модели, класс должен иметь общедоступный конструктор по умолчанию и общедоступные свойства записи для связывания.Когда происходит привязка модели, экземпляр класса создается с помощью общедоступного конструктора по умолчанию, а затем можно установить свойства.

Поэтому для успешного связывания необходимо:

  1. Иметь общедоступное значение по умолчаниюc-tor
  2. Есть открытые сеттеры для свойств.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...