Передать массив целых чисел в ASP.NET Web API? - PullRequest
372 голосов
/ 02 апреля 2012

У меня есть служба REST ASP.NET Web API (версия 4), где мне нужно передать массив целых чисел.

Вот мой метод действия:

public IEnumerable<Category> GetCategories(int[] categoryIds){
// code to retrieve categories from database
}

И этоURL, который я пробовал:

/Categories?categoryids=1,2,3,4

Ответы [ 15 ]

539 голосов
/ 19 июня 2012

Вам просто нужно добавить [FromUri] перед параметром, выглядит так:

GetCategories([FromUri] int[] categoryIds)

И отправить запрос:

/Categories?categoryids=1&categoryids=2&categoryids=3 
91 голосов
/ 01 октября 2013

Как указывает Филип W , вам, возможно, придется прибегнуть к пользовательскому подшивщику модели, подобному этому (модифицированному для привязки к фактическому типу параметра):

public IEnumerable<Category> GetCategories([ModelBinder(typeof(CommaDelimitedArrayModelBinder))]long[] categoryIds) 
{
    // do your thing
}

public class CommaDelimitedArrayModelBinder : IModelBinder
{
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        var key = bindingContext.ModelName;
        var val = bindingContext.ValueProvider.GetValue(key);
        if (val != null)
        {
            var s = val.AttemptedValue;
            if (s != null)
            {
                var elementType = bindingContext.ModelType.GetElementType();
                var converter = TypeDescriptor.GetConverter(elementType);
                var values = Array.ConvertAll(s.Split(new[] { ","},StringSplitOptions.RemoveEmptyEntries),
                    x => { return converter.ConvertFromString(x != null ? x.Trim() : x); });

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

                values.CopyTo(typedValues, 0);

                bindingContext.Model = typedValues;
            }
            else
            {
                // change this line to null if you prefer nulls to empty arrays 
                bindingContext.Model = Array.CreateInstance(bindingContext.ModelType.GetElementType(), 0);
            }
            return true;
        }
        return false;
    }
}

И тогда вы можете сказать:

/Categories?categoryids=1,2,3,4 и ASP.NET Web API корректно свяжут ваш categoryIds массив.

37 голосов
/ 09 июля 2013

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

public class ArrayInputAttribute : ActionFilterAttribute
{
    private readonly string _parameterName;

    public ArrayInputAttribute(string parameterName)
    {
        _parameterName = parameterName;
        Separator = ',';
    }

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (actionContext.ActionArguments.ContainsKey(_parameterName))
        {
            string parameters = string.Empty;
            if (actionContext.ControllerContext.RouteData.Values.ContainsKey(_parameterName))
                parameters = (string) actionContext.ControllerContext.RouteData.Values[_parameterName];
            else if (actionContext.ControllerContext.Request.RequestUri.ParseQueryString()[_parameterName] != null)
                parameters = actionContext.ControllerContext.Request.RequestUri.ParseQueryString()[_parameterName];

            actionContext.ActionArguments[_parameterName] = parameters.Split(Separator).Select(int.Parse).ToArray();
        }
    }

    public char Separator { get; set; }
}

Я применяю его так (обратите внимание, что я использовал 'id', а не 'идентификаторы ', как это указано в моем маршруте):

[ArrayInput("id", Separator = ';')]
public IEnumerable<Measure> Get(int[] id)
{
    return id.Select(i => GetData(i));
}

И публичный URL будет:

/api/Data/1;2;3;4

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

22 голосов
/ 13 марта 2015

В случае, если кому-то понадобится - добиться того же или похожего (например, удалить) с помощью POST вместо FromUri, используйте FromBody, а на стороне клиента (JS / jQuery) в качестве параметра укажите $.param({ '': categoryids }, true)

c #:

public IHttpActionResult Remove([FromBody] int[] categoryIds)

jQuery:

$.ajax({
        type: 'POST',
        data: $.param({ '': categoryids }, true),
        url: url,
//...
});

С $.param({ '': categoryids }, true) дело в том, что .net будет ожидать, что тело сообщения будет содержать значение urlencoded, например =1&=2&=3 безимя параметра и без скобок.

20 голосов
/ 05 апреля 2017

Простой способ отправки параметров массива в веб-API

API

public IEnumerable<Category> GetCategories([FromUri]int[] categoryIds){
 // code to retrieve categories from database
}

Jquery: отправка JSON-объекта в качестве параметров запроса

$.get('api/categories/GetCategories',{categoryIds:[1,2,3,4]}).done(function(response){
console.log(response);
//success response
});

Будет сгенерирован ваш запросURL как ../api/categories/GetCategories?categoryIds=1&categoryIds=2&categoryIds=3&categoryIds=4

10 голосов
/ 03 апреля 2012

Вы можете попробовать этот код, чтобы взять значения, разделенные запятыми / массив значений, чтобы получить JSON из webAPI

 public class CategoryController : ApiController
 {
     public List<Category> Get(String categoryIDs)
     {
         List<Category> categoryRepo = new List<Category>();

         String[] idRepo = categoryIDs.Split(',');

         foreach (var id in idRepo)
         {
             categoryRepo.Add(new Category()
             {
                 CategoryID = id,
                 CategoryName = String.Format("Category_{0}", id)
             });
         }
         return categoryRepo;
     }
 }

 public class Category
 {
     public String CategoryID { get; set; }
     public String CategoryName { get; set; }
 } 

Вывод:

[
{"CategoryID":"4","CategoryName":"Category_4"}, 
{"CategoryID":"5","CategoryName":"Category_5"}, 
{"CategoryID":"3","CategoryName":"Category_3"} 
]
6 голосов
/ 05 апреля 2018

Решение ASP.NET Core 2.0 (Swagger Ready)

Введите

DELETE /api/items/1,2
DELETE /api/items/1

код

Напишите провайдера (как MVC знает, какое связующее использовать)

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

        if (context.Metadata.ModelType == typeof(int[]) || context.Metadata.ModelType == typeof(List<int>))
        {
            return new BinderTypeModelBinder(typeof(CommaDelimitedArrayParameterBinder));
        }

        return null;
    }
}

Напишите фактическое связующее (доступ ко всей информации о запросе, действии, моделях, типах и т. Д.)

public class CommaDelimitedArrayParameterBinder : IModelBinder
{

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {

        var value = bindingContext.ActionContext.RouteData.Values[bindingContext.FieldName] as string;

        // Check if the argument value is null or empty
        if (string.IsNullOrEmpty(value))
        {
            return Task.CompletedTask;
        }

        var ints = value?.Split(',').Select(int.Parse).ToArray();

        bindingContext.Result = ModelBindingResult.Success(ints);

        if(bindingContext.ModelType == typeof(List<int>))
        {
            bindingContext.Result = ModelBindingResult.Success(ints.ToList());
        }

        return Task.CompletedTask;
    }
}

Зарегистрируйте его в MVC

services.AddMvc(options =>
{
    // add custom binder to beginning of collection
    options.ModelBinderProviders.Insert(0, new CustomBinderProvider());
});

Пример использования с хорошо документированным контроллером для Swagger

/// <summary>
/// Deletes a list of items.
/// </summary>
/// <param name="itemIds">The list of unique identifiers for the  items.</param>
/// <returns>The deleted item.</returns>
/// <response code="201">The item was successfully deleted.</response>
/// <response code="400">The item is invalid.</response>
[HttpDelete("{itemIds}", Name = ItemControllerRoute.DeleteItems)]
[ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(void), StatusCodes.Status404NotFound)]
public async Task Delete(List<int> itemIds)
=> await _itemAppService.RemoveRangeAsync(itemIds);

РЕДАКТИРОВАТЬ: Microsoft рекомендует использовать TypeConverter для этих детей операций по сравнению с этим подходом. Поэтому следуйте советам, приведенным ниже, и документируйте свой тип с помощью SchemaFilter.

6 голосов
/ 29 марта 2018

Я изначально использовал решение, которое @Mrchief годами (прекрасно работает). Но когда я добавил Swagger в свой проект для документации по API, моей конечной точкой было НЕ .

Мне потребовалось некоторое время, но это то, что я придумал. Он работает с Swagger, и ваши сигнатуры методов API выглядят чище:

В конце концов вы можете сделать:

    // GET: /api/values/1,2,3,4 

    [Route("api/values/{ids}")]
    public IHttpActionResult GetIds(int[] ids)
    {
        return Ok(ids);
    }

WebApiConfig.cs

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Allow WebApi to Use a Custom Parameter Binding
        config.ParameterBindingRules.Add(descriptor => descriptor.ParameterType == typeof(int[]) && descriptor.ActionDescriptor.SupportedHttpMethods.Contains(HttpMethod.Get)
                                                           ? new CommaDelimitedArrayParameterBinder(descriptor)
                                                           : null);

        // Allow ApiExplorer to understand this type (Swagger uses ApiExplorer under the hood)
        TypeDescriptor.AddAttributes(typeof(int[]), new TypeConverterAttribute(typeof(StringToIntArrayConverter)));

        // Any existing Code ..

    }
}

Создать новый класс: CommaDelimitedArrayParameterBinder.cs

public class CommaDelimitedArrayParameterBinder : HttpParameterBinding, IValueProviderParameterBinding
{
    public CommaDelimitedArrayParameterBinder(HttpParameterDescriptor desc)
        : base(desc)
    {
    }

    /// <summary>
    /// Handles Binding (Converts a comma delimited string into an array of integers)
    /// </summary>
    public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider,
                                             HttpActionContext actionContext,
                                             CancellationToken cancellationToken)
    {
        var queryString = actionContext.ControllerContext.RouteData.Values[Descriptor.ParameterName] as string;

        var ints = queryString?.Split(',').Select(int.Parse).ToArray();

        SetValue(actionContext, ints);

        return Task.CompletedTask;
    }

    public IEnumerable<ValueProviderFactory> ValueProviderFactories { get; } = new[] { new QueryStringValueProviderFactory() };
}

Создать новый класс: StringToIntArrayConverter.cs

public class StringToIntArrayConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
    }
}

Примечания:

  • https://stackoverflow.com/a/47123965/862011 указал мне в правильном направлении
  • Swagger не смог выбрать конечные точки с разделителями-запятыми при использовании атрибута [Route]
6 голосов
/ 15 июля 2014
public class ArrayInputAttribute : ActionFilterAttribute
{
    private readonly string[] _ParameterNames;
    /// <summary>
    /// 
    /// </summary>
    public string Separator { get; set; }
    /// <summary>
    /// cons
    /// </summary>
    /// <param name="parameterName"></param>
    public ArrayInputAttribute(params string[] parameterName)
    {
        _ParameterNames = parameterName;
        Separator = ",";
    }

    /// <summary>
    /// 
    /// </summary>
    public void ProcessArrayInput(HttpActionContext actionContext, string parameterName)
    {
        if (actionContext.ActionArguments.ContainsKey(parameterName))
        {
            var parameterDescriptor = actionContext.ActionDescriptor.GetParameters().FirstOrDefault(p => p.ParameterName == parameterName);
            if (parameterDescriptor != null && parameterDescriptor.ParameterType.IsArray)
            {
                var type = parameterDescriptor.ParameterType.GetElementType();
                var parameters = String.Empty;
                if (actionContext.ControllerContext.RouteData.Values.ContainsKey(parameterName))
                {
                    parameters = (string)actionContext.ControllerContext.RouteData.Values[parameterName];
                }
                else
                {
                    var queryString = actionContext.ControllerContext.Request.RequestUri.ParseQueryString();
                    if (queryString[parameterName] != null)
                    {
                        parameters = queryString[parameterName];
                    }
                }

                var values = parameters.Split(new[] { Separator }, StringSplitOptions.RemoveEmptyEntries)
                    .Select(TypeDescriptor.GetConverter(type).ConvertFromString).ToArray();
                var typedValues = Array.CreateInstance(type, values.Length);
                values.CopyTo(typedValues, 0);
                actionContext.ActionArguments[parameterName] = typedValues;
            }
        }
    }

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        _ParameterNames.ForEach(parameterName => ProcessArrayInput(actionContext, parameterName));
    }
}

Использование:

    [HttpDelete]
    [ArrayInput("tagIDs")]
    [Route("api/v1/files/{fileID}/tags/{tagIDs}")]
    public HttpResponseMessage RemoveFileTags(Guid fileID, Guid[] tagIDs)
    {
        _FileRepository.RemoveFileTags(fileID, tagIDs);
        return Request.CreateResponse(HttpStatusCode.OK);
    }

Запрос uri

http://localhost/api/v1/files/2a9937c7-8201-59b7-bc8d-11a9178895d0/tags/BBA5CD5D-F07D-47A9-8DEE-D19F5FA65F63,BBA5CD5D-F07D-47A9-8DEE-D19F5FA65F63
5 голосов
/ 05 февраля 2018

Вместо использования пользовательского ModelBinder вы также можете использовать пользовательский тип с TypeConverter.

[TypeConverter(typeof(StrListConverter))]
public class StrList : List<string>
{
    public StrList(IEnumerable<string> collection) : base(collection) {}
}

public class StrListConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (value == null)
            return null;

        if (value is string s)
        {
            if (string.IsNullOrEmpty(s))
                return null;
            return new StrList(s.Split(','));
        }
        return base.ConvertFrom(context, culture, value);
    }
}

Преимущество состоит в том, что он делает параметры метода Web API очень простыми.Вам даже не нужно указывать [FromUri].

public IEnumerable<Category> GetCategories(StrList categoryIds) {
  // code to retrieve categories from database
}

Этот пример для списка строк, но вы можете сделать categoryIds.Select(int.Parse) или просто написать IntList вместо этого.

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