Минимальный API требует TryParse() для модели ввода, несмотря на ModelBinder

Я пытаюсь реализовать один ModelBinder для всех моих DTO:

public class MyModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext) {
        var queryDto = bindingContext.ModelType.GetConstructors()[0].Invoke([]);
        // fill properties via Reflection
        bindingContext.Result = ModelBindingResult.Success(queryDto);
        return Task.CompletedTask;
    }
}

Это пример DTO:

public class Dto {
    public int Id { get; set; }
    public string Name { get; set; }
}

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

app.MapGet("/get-dto", ([FromQuery] [ModelBinder(typeof(MyModelBinder))] Dto dto) => {
    return CalculateResultSomehow(dot);
});

Компилятор выдает мне ошибку:

ошибка ASP0020: параметр dto типа Dto должен определить метод bool TryParse(string, IFormatProvider, out Dto) или реализовать IParsable.

Если я удалю атрибут [FromQuery], лямбда выдаст предупреждение:

ModelBinderAttribute не следует указывать для параметра MapGet Delegate.

И код прерывается во время выполнения с исключением:

При обработке запроса произошло необработанное исключение. InvalidOperationException: тело было выведено, но метод не позволяет выводить параметры тела... Вы имели в виду зарегистрировать параметр(ы) «Body (Inferred)» как Службу или применить атрибут [FromServices] или [FromBody]?

Теперь, поскольку я реализую логику синтаксического анализа, основанную на Reflection, я не хочу реализовывать статику TryParse() для каждого отдельного DTO моего приложения (у меня их 100...). А мне не следует: ModelBinder у меня уже есть.

Действие контроллера прекрасно работает с использованием той же системы:

[ApiController]
public class MyController
{
    [HttpGet("/get-dto")]
    public Dto GetDto([FromQuery] [ModelBinder(typeof(MyModelBinder))] Dto dto) {
        return dto;
    }
}

Я потерялся здесь. Что мне не хватает? Почему это не работает для минимальных API?

"почему"? По той же причине все используют Reflection: чтобы написать преобразование/синтаксический анализ/алгоритм один раз. Я не хочу писать <если строка запроса имеет ключ «имя», установите свойство Name; если у него есть «код», установите свойство Code ecc. ecc.> для каждого свойства каждого DTO — это кошмар. Я хочу сделать это через Reflection для одного класса: <ключ foreach в строке запроса, если у DTO есть свойство с таким же именем, попробуйте установить его значение>.

Massimiliano Kraus 09.05.2024 01:11

Пожалуйста, ознакомьтесь с обновлением в ответе.

Guru Stron 09.05.2024 01:48

Привет @Massimiliano Kraus, ASP.NET Core поддерживает [AsParameters] привязку сложной модели вместо вложенной модели. По вашему требованию меня смущает, почему вы связываете вложенную модель с телом или с формой? Я думаю, что длина запроса будет ограничена браузером или веб-сервером, а также небезопасно, чтобы конфиденциальная информация могла быть раскрыта.

Rena 09.05.2024 10:01

В чем разница между «сложной моделью» и «вложенной моделью»? Вызов, извлекающий данные, должен использовать метод GET, поэтому вам следует передавать параметры в пути и в запросе, поскольку вы не можете использовать тело. [FromQuery] считывает параметры из строки запроса. [AsParameters] не работает, если мой DTO имеет вложенные классы (например: PaginationDto имеет фильтры свойств, которые представляют собой список FilterDto, и каждый FilterDto имеет PropertyName и PropertyValue...)

Massimiliano Kraus 09.05.2024 12:56
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
338
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я потерялся здесь. Что мне не хватает? Почему это не работает для минимальных API?

Потому что минимальные API не поддерживают «обычные» привязки моделей. Привязки моделей являются частью «полной» структуры (что можно косвенно заключить из пространства имен ModelBinderAttributeMicrosoft.AspNetCore.Mvc).

Сведения о привязке, поддерживаемой минимальными API, см. в документе Привязка параметров в приложениях с минимальным API.

поскольку я реализую логику синтаксического анализа, основанную на Reflection

Не видя строк запроса и логики отражения, трудно сказать, но обычно такая логика вам не нужна. Но повторюсь – трудно сказать.

Я не хочу реализовывать статический TryParse()

В качестве обходного пути для минимальных API вы можете создать некоторый класс-оболочку, например MinimalApiBinder<T>, и один раз реализовать в нем логику привязки. Примеры см. в разделе Как настроить NewtonsoftJson с минимальным API в .NET 6.0.

УПД

Из комментария:

Я не хочу писать <если строка запроса имеет ключ «имя», установите свойство Name; если у него есть «код», установите свойство Code ecc. и т.д.>

Это должно работать из коробки. Например, с атрибутом AsParameters (см. ранее связанные документы):

app.MapGet("/get-dto", ([AsParameters] Dto dto) => dto);

Будет правильно привязан из строки запроса /get-dto?name=test&id=1.

[AsParameters] работает, но только если DTO «плоский», т. е. имеет только свойства первого уровня. Если у него есть вложенные свойства, выдается исключение: «System.InvalidOperationException: тело было выведено, но метод не допускает выводимые параметры тела».... Я не знаю, смогу ли я решить эту проблему с помощью атрибутов во вложенных полях DTO, но я не хочу, чтобы мне приходилось повсюду отмечать свои DTO атрибутами. Это должно работать из коробки. Я оцениваю другие предложенные вами решения.
Massimiliano Kraus 09.05.2024 07:37

@MassimilianoKraus «DTO является «плоским», т.е. имеет свойства только первого уровня». - это описано в связанных документах: «AsParametersAttribute обеспечивает простую привязку параметров к типам, а не сложную или рекурсивную привязку модели». «Это должно работать из коробки» — это зависит от того, что вы подразумеваете под «должно». Согласно текущей документации/реализации - этого не должно быть, минимальные API не зря названы так. Если вам нужны «продвинутые» сценарии привязки — я бы рекомендовал придерживаться контроллеров.

Guru Stron 09.05.2024 23:51

@MassimilianoKraus Также одна из ссылок в связанном ответе о Newtonsoft указывает на способ повторного использования связующих моделей MVC - ModelBinderOfT @github

Guru Stron 09.05.2024 23:52

«Не должно быть, минимальные API не зря названы так». ... ну... функция ModelBinder уже реализована, не думаю, что было бы так сложно включить ее и в минимальные API... Возможно, есть более серьезная проблема: я не могу найти четкая официальная спецификация того, как должна быть структурирована строка запроса, чтобы иметь вложенные свойства. Похоже, что строки запроса никогда не рассматривались как нечто большее, чем простой список пар ключ-значение. Microsoft принимает множество разных форматов (с [], с индексами, с повторяющимися ключами...), но какой из них является официальным?

Massimiliano Kraus 13.05.2024 10:48

@MassimilianoKraus «Я не думаю, что было бы так сложно включить его в минимальные API» - насколько я понимаю, одной из основных целей внедрения минимальных API было значительное повышение производительности, поэтому они были почти полностью переписаны. .

Guru Stron 13.05.2024 10:58

Я нашел решение, подходящее для моего случая, но +1 за вашу поддержку и полезную информацию!

Massimiliano Kraus 13.05.2024 11:31
Ответ принят как подходящий

Поскольку у меня уже был динамически сгенерированный набор конечных точек, основанный на нескольких общих универсальных методах, я объявил строку запроса как простой строковый параметр и отправил преобразование «строка запроса => типизированный dto» после начала метода, вот так :

// Simplified version:
public static Delegate ConfigureEndpoint<TQueryStringDto>()
{
    return async ([FromQuery] string query /* other params omitted */) => {
        var dto = ConvertToDto<TQueryStringDto>(query);
        // Do something general, valid for every endpoint,
        // like sending the dto to IMediator
    };
}

ConvertToDto — это общий метод, который я бы использовал в раннем промежуточном программном обеспечении или в ModelBinder, но я также могу использовать его здесь, немного ниже цепочки обработки запроса.

Другие вопросы по теме