Пользовательское связывание модели для метода webapi с более чем одним параметром - PullRequest
2 голосов
/ 04 июля 2019

ЧТО Я ИМЕЮ

У меня есть контроллер API (ASP.NET Core MVC) с использованием следующего метода:

[HttpPost]
[Route("delete")]
public Task<ActionResult> SomeAction(Guid[] ids,  UserToken userToken, CancellationToken cancellationToken)
{
   ....
}

У меня есть пользовательский механизм связывания моделейи поставщик связывателя:

public class UserTokenBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (context.Metadata.ModelType == typeof(UserToken))
        {
            return new BinderTypeModelBinder(typeof(UserTokenBinder));
        }

        return null;
    }
}

public class UserTokenBinder: IModelBinder
{
    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var token = await bindingContext.ActionContext.HttpContext.User.ToUserTokenAsync(CancellationToken.None);

        bindingContext.Result = ModelBindingResult.Success(token ?? UserToken.UnidentifiedUser);
    }
}

Добавлен поставщик связывателя к службам:

services.AddMvc(options =>
{                    
    options.ModelBinderProviders.Insert(0, new UserTokenBinderProvider());
});

ПРОБЛЕМА

Во время загрузки сервера яполучить следующее исключение (InvalidOperationException):

... SomeAction имеет более одного параметра, который был указан или выведен как связанный с телом запроса.Только один параметр на действие может быть связан с телом.Проверьте следующие параметры и используйте «FromQueryAttribute» для указания границы из запроса, «FromRouteAttribute» для указания границы из маршрута и «FromBodyAttribute» для параметров, которые должны быть связаны с телом: Guid [] ids, UserToken userToken

Кажется, что MVC игнорирует пользовательское связующее, которое у меня есть для типа UserToken, и пытается связать его, используя методы по умолчанию.Есть идеи, почему?

РЕДАКТИРОВАТЬ После получения ответа здесь, выпуск был открыт для изменения документации ASP.NET Core.

1 Ответ

2 голосов
/ 04 июля 2019

Наличие атрибута [ApiController] вводит Вывод параметра источника привязки для параметров действия. При запуске соглашение модели действия выполняется для всех обнаруженных действий контроллера и определяет источники привязки. Для сложных типов, таких как ваши параметры Guid[] и UserToken, этот вывод выбирает тело запроса в качестве источника - как если бы вы добавили [FromBody] к обоим этих параметров самостоятельно, например, это:

public Task<ActionResult> SomeAction(
    [FromBody] Guid[] ids,
    [FromBody] UserToken userToken,
    CancellationToken cancellationToken)

В своем вопросе вы утверждаете:

Кажется, что MVC игнорирует пользовательское связующее, которое у меня есть для типа UserToken, и пытается связать его, используя методы по умолчанию.

Это не совсем то, что здесь происходит. Он еще ничего не пытается связать - он просто пытается настроить источники привязки при запуске, прежде чем может произойти привязка модели. Вы правильно проинструктировали MVC использовать ваше пользовательское связующее для модели, но упомянутое выше соглашение о модели действия ничего не знает о добавленном вами IModelBinderProvider. Даже если это так, фактическая связь между поставщиком связывателя модели и типом (UserToken) не известна до тех пор, пока не будет запущен метод GetBinder, что происходит только тогда, когда необходимо связывание модели; не запускается при настройке модели приложения.

Если вы обновите свой класс UserToken, добавив атрибут [ModelBinder], все будет работать (вы даже можете удалить UserTokenBinderProvider):

[ModelBinder(typeof(UserTokenBinderProvider))]
public class UserToken { }

Большим недостатком этого подхода является то, что ваш класс UserToken будет зависеть от атрибута MVC, который может оказаться не тем, что вам нужно. Итак, есть что-то лучше?

Теперь вам может быть интересно, почему я не показал [FromBody] для параметра CancellationToken выше. Значит ли это, что CancellationToken получает специальное лечение? Да, это . BindingSourceMetadataProvider добавляется к экземпляру MvcOptions, который указывает его источник привязки как BindingSource.Special. Когда соглашение модели действия выполняется и пытается определить источник привязки, он видит, что источник привязки уже установлен, и оставляет его в покое .

Чтобы решить вашу проблему, добавьте BindingSourceMetadataProvider для вашего UserToken типа и используйте BindingSource.Special, например:

services.AddMvc(options =>
{                    
    options.ModelBinderProviders.Insert(0, new UserTokenBinderProvider());
    options.ModelMetadataDetailsProviders.Add(
        new BindingSourceMetadataProvider(typeof(UserToken), BindingSource.Special));
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...