Как отключить автоматическую привязку модели для определенного действия веб-API ASP.NET Core 5.0?

У меня есть стороннее проприетарное приложение, для которого мне нужно написать конечную точку API в моем приложении веб-API ASP.NET Core 5.0.

Стороннее приложение отправляет почтовый запрос HTTP, содержащий только двоичные данные в теле запроса, наряду с типом контента application/x-www-form-urlencoded или, иногда, application/octet-stream (случайно, но данные одинаковы).

Мой обработчик действий выглядит так:

[Route("~/Validation")]
[ApiController]
public class ValidationController : ControllerBase
{
    [HttpPost("{requestId}")]
    [Consumes(@"application/octet-stream", @"application/x-www-form-urlencoded")]
    [Produces(@"application/octet-stream")]
    public async Task<IActionResult> Validation_Post([FromRoute] string requestId)
    {
        byte[] rawRequestBody = Array.Empty<byte>();
        {
            long streamInitialPos = 0;
            if (Request.Body.CanSeek) // rewind for this read.
            {
                streamInitialPos = Request.Body.Position;
                Request.Body.Seek(0, SeekOrigin.Begin);
            }
            using (var ms = new MemoryStream())
            {
                await Request.Body.CopyToAsync(ms);
                rawRequestBody = ms.ToArray() ?? throw new NullReferenceException();
            }
            if (Request.Body.CanSeek) // rewind to initial position.
                Request.Body.Seek(streamInitialPos, SeekOrigin.Begin);
        }

        // TODO: Handle rawRequestBody data.

        return new FileContentResult(new byte[] { 1 }, @"application/octet-stream")
        {
            EnableRangeProcessing = true,
            LastModified = DateTime.UtcNow
        };
    }

Когда стороннее приложение отправляет свой почтовый запрос HTTP на конечную точку моего API, мое приложение API аварийно завершает работу с System.ArgumentException:

Microsoft.AspNetCore.Server.IIS.Core.IISHttpServer: Error: Connection ID "18374686481282236432", Request ID "80000011-0000-ff00-b63f-84710c7967bb": An unhandled exception was thrown by the application.

System.ArgumentException: The key '[omitted binary data]' is invalid JQuery syntax because it is missing a closing bracket. (Parameter 'key')
   at Microsoft.AspNetCore.Mvc.ModelBinding.JQueryKeyValuePairNormalizer.NormalizeJQueryToMvc(StringBuilder builder, String key)
   at Microsoft.AspNetCore.Mvc.ModelBinding.JQueryKeyValuePairNormalizer.GetValues(IEnumerable`1 originalValues, Int32 valueCount)
   at Microsoft.AspNetCore.Mvc.ModelBinding.JQueryFormValueProviderFactory.AddValueProviderAsync(ValueProviderFactoryContext context)
   at Microsoft.AspNetCore.Mvc.ModelBinding.CompositeValueProvider.CreateAsync(ActionContext actionContext, IList`1 factories)
   at Microsoft.AspNetCore.Mvc.ModelBinding.CompositeValueProvider.TryCreateAsync(ActionContext actionContext, IList`1 factories)
   at Microsoft.AspNetCore.Mvc.Controllers.ControllerBinderDelegateProvider.<>c__DisplayClass0_0.<<CreateBinderDelegate>g__Bind|0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Server.IIS.Core.IISHttpContextOfT`1.ProcessRequestAsync()

Microsoft.AspNetCore.Hosting.Diagnostics: Information: Request finished HTTP/1.1 POST http://localhost:10891/validation/dummy application/x-www-form-urlencoded 11072 - 500 - - 164.0024ms

Журналы показывают, что используется правильное действие маршрута.

Как отключить автоматическую привязку модели только для этого конкретного обработчика действий?

Напоминание: я не могу вносить изменения в стороннее приложение. Я должен справиться с тем, что я получаю. Я знаю, что тип содержимого запроса неверен. Пожалуйста, не делайте никаких заметок по этому поводу.


Изменить: я обнаружил поверхностную причину этой ошибки. Когда я удаляю [FromRoute] string requestId из сигнатуры функции, ошибка не возникает. Когда я повторно ввожу его, ошибка повторяется.

Не работает (вызывает внутреннее исключение ASP.NET Core):

public async Task<IActionResult> Validation_Post([FromRoute] string requestId)

Работает:

public async Task<IActionResult> Validation_Post()

Однако мне нужно получить доступ к переменной маршрута через Request.RouteValues["requestId"].

В любом случае, вопрос остается в силе: Как отключить автоматическую привязку модели только для этого конкретного обработчика действий?


person burnersk    schedule 19.12.2020    source источник
comment
Какая строка выдает этот ArgumentException? А откуда stream?   -  person mxmissile    schedule 19.12.2020
comment
@mxmissile: Спасибо, что указали на stream, я настолько сократил код, что добавил ошибки во фрагмент кода. Исходный код действия теперь исправлен, и я добавил в вопрос полную трассировку стека.   -  person burnersk    schedule 19.12.2020
comment
@mxmissile: И очереди нет. Думаю, это внутреннее ядро ​​ASP.NET (фреймворк).   -  person burnersk    schedule 19.12.2020
comment
Что-то здесь не так, можете ли вы переименовать свой контроллер, выполнить маршрутизацию и протестировать еще раз?   -  person mxmissile    schedule 19.12.2020
comment
Можете ли вы переместить эту конечную точку на другой контроллер, где вы опускаете [ApiController]? Я никогда раньше не видел, чтобы ~ использовалось в маршрутизации, но если это работает, это хорошо для вас.   -  person Roar S.    schedule 19.12.2020
comment
@mxmissile: я нашел поверхностную причину этого, но первоначальный вопрос остается в силе. Смотрите обновление моего вопроса.   -  person burnersk    schedule 20.12.2020
comment
@RoarS.: ~ относится к корневой точке mount (в отличие от / для фактического корневого пути). Удаление [ApiController] не помогло, см. обновление моего вопроса.   -  person burnersk    schedule 20.12.2020
comment
Не уверен, почему JQuery вообще вовлечен в это внутренне, это кричит об именах, особенно о проверке. Можете ли вы переименовать его и проверить с чем-то другим?   -  person mxmissile    schedule 21.12.2020
comment
@mxmissile: уже пробовал, с foo (контроллер) и bar (действие), без изменений   -  person burnersk    schedule 21.12.2020
comment
@mxmissile: классы внутренней привязки модели ASP.NET Core включают специальную обработку для определенных соглашений об именах полей jQuery. Взгляните на NormalizeJQueryToMvc(), который отвечает за создание этого исключения. Это относится к тому, как поля сопоставляются с объектами во время привязки данных. Этот класс ищет синтаксис JQuery и нормализует его для использования синтаксиса MVC, чтобы остальная часть привязки модели не требовала учета различий.   -  person Jeremy Caney    schedule 25.12.2020
comment
@JeremyCaney спасибо, что прояснили это.   -  person mxmissile    schedule 26.12.2020


Ответы (1)


Думаю, вы уже нашли решение или, по крайней мере, определили все ключевые компоненты. Позвольте мне пройтись по ним, чтобы вы могли собрать их в решение.

Отключение привязки модели

Привязка модели происходит только в том случае, если у вас есть параметры для вашего действия — в противном случае нет модели для привязки. Это может быть настоящая модель, такая как POCO, или просто тип значения, такой как строковый параметр, который у вас есть здесь; один и тот же основной процесс применяется к обоим. Вот почему вы не получаете сообщение об ошибке при удалении параметра, а также почему вы уже эффективно ответили на свой вопрос.

Извлечение значений маршрута без привязки модели

Единственная цель или преимущество наличия параметра — участие в привязке модели. Поэтому, если вы хотите отключить привязку модели, нет причин поддерживать этот параметр. Без него вы по-прежнему можете использовать свойство Request для извлечения значений запроса, включая поля формы, параметры строки запроса, переменные маршрута, заголовки запроса и т. д. Итак, в этом случае вы все равно можете вызывать, например, Request.RouteValues["requestId"] в своем действии, как вы заметили. Выполнение вызовов непосредственно к RouteValueDictionary не приведет к вызову класса JQueryKeyValuePairNormalizer, поэтому вы не столкнетесь с тем же исключением.

Возможная неоднозначность маршрута

Тем не менее, в зависимости от того, как определены ваш маршрут и параметры маршрута, вы можете столкнуться с проблемами, если вы, например, полагаетесь на этот параметр для устранения неоднозначности между другими перегрузками вашего действия, поэтому об этом стоит знать. .

Основная проблема

Однако, чтобы вернуться к основной ошибке, я бы также порекомендовал оценить данные вашего запроса о неудачных запросах, чтобы убедиться, что нет ничего, что можно было бы спутать с именем поля, содержащим неправильно сформированный индексатор. Из того, что вы описываете, я бы не ожидал, что это произойдет, если только он каким-то образом не пытался проанализировать двоичные данные как пары ключ/значение, ожидаемые от данных формы, возможно, из-за ошибочного типа содержимого. Несмотря на это, строка кода, выдающего ошибку, возникает, в частности, когда есть поле, содержащее [ без соответствующего ]. Например, это должно произойти со следующей строкой запроса:

?key[0=5

Если что-то подобное постоянно присутствует в ваших входящих запросах, скорее всего, это и есть причина. Очевидно, что вы ничего не можете с этим поделать, так как вы не контролируете клиента, но было бы полезно изолировать, чтобы вы могли предложить руководство для будущих реализаций.

person Jeremy Caney    schedule 25.12.2020
comment
Спасибо. У меня было предположение, что привязка модели будет настолько умной, что она не будет пытаться анализировать тело, когда в сигнатуре функции есть только [FromRoute] (контекст/источник не является телом). - person burnersk; 27.12.2020
comment
@burnersk: Я слышу тебя. Это не только кажется, что это должно быть логичным предположением, но можно было бы также ожидать, что оно будет (незначительно) более производительным, поскольку не нужно (дальнейшим) анализировать содержимое, которое не понадобится для привязки данных. В моей компании есть написанная нами библиотека сопоставления, которая очень похожа на привязку модели, и именно по этой причине она использует более реактивный подход. - person Jeremy Caney; 27.12.2020