Массив привязки модели веб-API ASP.NET Core 1

Как вы моделируете привязку массива из URI с помощью GET в веб-API ASP.NET Core 1 (неявно или явно)?

В веб-API ASP.NET pre Core 1 это работало:

[HttpGet]
public void Method([FromUri] IEnumerable<int> ints) { ... }

Как это сделать в ASP.NET Web API Core 1 (также известном как ASP.NET 5 или ASP.NET vNext)? В документах ничего нет.


person marras    schedule 22.04.2016    source источник
comment
хех, @bzlm ты правда потратил 100 репутации только на то, чтобы посмотреть, правильно ли мы параметризуем наши входные данные?   -  person Marc Gravell    schedule 29.04.2016
comment
@MarcGravell не сработал бы, если бы вы этого не сделали. Он забыл начать комментарий в конце --   -  person    schedule 29.04.2016
comment
@MarcGravell Стоит 100 мнимых очков :)   -  person Muhammad Rehan Saeed    schedule 27.04.2017


Ответы (3)


Класс FromUriAttribute объединяет классы FromRouteAttribute и FromQueryAttribute. В зависимости от конфигурации ваших маршрутов / отправляемого запроса вы сможете заменить свой атрибут одним из них.

Однако есть прокладка, которая даст вам класс FromUriAttribute. Установите пакет NuGet Microsoft.AspNet.Mvc.WebApiCompatShim через проводник пакетов или добавьте его непосредственно в файл project.json:

"dependencies": {
  "Microsoft.AspNet.Mvc.WebApiCompatShim": "6.0.0-rc1-final"
}

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

Привязка

Если вы хотите связать значения, разделенные запятыми, для массива (/ api / values? Ints = 1,2,3), вам, как и раньше, понадобится настраиваемое связующее. Это адаптированная версия решения Mrchief для использования в ASP.NET Core.

public class CommaDelimitedArrayModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext.ModelMetadata.IsEnumerableType)
        {
            var key = bindingContext.ModelName;
            var value = bindingContext.ValueProvider.GetValue(key).ToString();

            if (!string.IsNullOrWhiteSpace(value))
            {
                var elementType = bindingContext.ModelType.GetTypeInfo().GenericTypeArguments[0];
                var converter = TypeDescriptor.GetConverter(elementType);

                var values = value.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)
                    .Select(x => converter.ConvertFromString(x.Trim()))
                    .ToArray();

                var typedValues = Array.CreateInstance(elementType, values.Length);

                values.CopyTo(typedValues, 0);
                
                bindingContext.Result = ModelBindingResult.Success(typedValues);
            }
            else
            {
                // change this line to null if you prefer nulls to empty arrays 
                bindingContext.Result = ModelBindingResult.Success(Array.CreateInstance(bindingContext.ModelType.GetElementType(), 0));
            }

            return TaskCache.CompletedTask;
        }

        return TaskCache.CompletedTask;
    }
}

Вы можете либо указать связыватель модели, который будет использоваться для всех коллекций в Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc().AddMvcOptions(opts =>
        {
            opts.ModelBinders.Insert(0, new CommaDelimitedArrayModelBinder());
        });
}

Или укажите его один раз в вызове API:

[HttpGet]
public void Method([ModelBinder(BinderType = typeof(CommaDelimitedArrayModelBinder))] IEnumerable<int> ints)
person Will Ray    schedule 29.04.2016
comment
Речь идет конкретно о привязке модели неявного массива. Использование FromQuery или FromRoute в ASP.NET Core 1 не включает привязку неявной модели массива, разделенной запятыми, для HttpGet (что происходит в веб-API ASP.NET и ASP.NET MVC). - person bzlm; 01.05.2016
comment
@bzlm В вопросе нет упоминания о привязке значений массива, разделенных запятыми. Откуда вы это берете? - person Will Ray; 01.05.2016
comment
Слово array находится в заголовке вопроса (привязка модели array), а в примере кода IEnumerable<int> ints является параметром действия. :) Отредактировал вопрос для ясности. Вы знаете ответ (используя FromQuery или нет)? - person bzlm; 02.05.2016
comment
Здесь нет упоминания о фразе, разделенной запятыми, поэтому я прошу внести ясность. Такой запрос, как /api/values?ints=1&ints=2&ints3, будет работать без каких-либо атрибутов. Для запроса типа /api/values?ints=1,2,3, как и раньше, потребуется привязка пользовательской модели. Я добавил связку к ответу. - person Will Ray; 02.05.2016
comment
Это больше не работает, я пробовал и пробовал .... и хотя modelBinder работает, параметр на контроллере говорит, что он равен нулю - person Gillardo; 02.12.2016
comment
В .net core 1.1 обновлен подпись интерфейса IModelBinder. См. Эту ссылку docs.microsoft.com/en -us / aspnet / core / mvc / advanced /, как это адаптировать. Добавьте в конец BindModelAsync Task.CompletedTask или TaskCache.CompletedTask и установите bindingContext.Result = ModelBindingResult.Success (typedValues) - person rock_walker; 21.07.2017

Ответ ASP.NET Core 1.1

Ответ @ WillRay немного устарел. Я написал IModelBinder и IModelBinderProvider. Первый можно использовать с атрибутом [ModelBinder(BinderType = typeof(DelimitedArrayModelBinder))], а второй - для глобального применения привязки модели, как я показал ниже.

.AddMvc(options =>
{
    // Add to global model binders so you don't need to use the [ModelBinder] attribute.
    var arrayModelBinderProvider = options.ModelBinderProviders.OfType<ArrayModelBinderProvider>().First();
    options.ModelBinderProviders.Insert(
        options.ModelBinderProviders.IndexOf(arrayModelBinderProvider),
        new DelimitedArrayModelBinderProvider());
})

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

        if (context.Metadata.IsEnumerableType && !context.Metadata.ElementMetadata.IsComplexType)
        {
            return new DelimitedArrayModelBinder();
        }

        return null;
    }
}

public class DelimitedArrayModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
        {
            throw new ArgumentNullException(nameof(bindingContext));
        }

        var modelName = bindingContext.ModelName;
        var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
        var values = valueProviderResult
            .ToString()
            .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
        var elementType = bindingContext.ModelType.GetTypeInfo().GenericTypeArguments[0];

        if (values.Length == 0)
        {
            bindingContext.Result = ModelBindingResult.Success(Array.CreateInstance(elementType, 0));
        }
        else
        {
            var converter = TypeDescriptor.GetConverter(elementType);
            var typedArray = Array.CreateInstance(elementType, values.Length);

            try
            {
                for (int i = 0; i < values.Length; ++i)
                {
                    var value = values[i];
                    var convertedValue = converter.ConvertFromString(value);
                    typedArray.SetValue(convertedValue, i);
                }
            }
            catch (Exception exception)
            {
                bindingContext.ModelState.TryAddModelError(
                    modelName,
                    exception,
                    bindingContext.ModelMetadata);
            }

            bindingContext.Result = ModelBindingResult.Success(typedArray);
        }

        return Task.CompletedTask;
    }
}
person Muhammad Rehan Saeed    schedule 27.04.2017
comment
Я пошел дальше и обновил свой исходный ответ, чтобы он работал в 1.1. Очень нравится решение, которое вы создали для провайдера! - person Will Ray; 28.07.2017
comment
bindingContext.ModelType.GetTypeInfo().GenericTypeArguments[0]; выдает исключение при связывании с string[], используя это вместо var elementType = bindingContext.ModelType.GetElementType(); - person JJS; 08.08.2017
comment
К сожалению, этот код не позволяет использовать один элемент, только предполагает, что всегда присутствует разделитель. - person mko; 15.04.2020

В .NET Core 3 внесены некоторые изменения.

Microsoft отделила функциональность от метода AddMvc (source).

Поскольку AddMvc также включает поддержку контроллеров представления, представлений Razor и т. Д. Если вам не нужно использовать их в своем проекте (например, в API), вы можете рассмотреть возможность использования services.AddControllers (), предназначенного для контроллеров веб-API.

Итак, обновленный код будет выглядеть так:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers()
            .AddMvcOptions(opt =>
            {
                var mbp = opt.ModelBinderProviders.OfType<ArrayModelBinderProvider>().First();
                    opt.ModelBinderProviders.Insert(opt.ModelBinderProviders.IndexOf(mbp), new DelimitedArrayModelBinderProvider());
            });
}
person Dimitri    schedule 11.12.2019