Передать массив целых чисел в ASP.NET Web API?

428

У меня есть служба 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
Хеманшу Бходжак
источник
1
При использовании строки запроса типа "/ Categories? Categoryids = 1 & categoryids = 2 & categoryids = 3" я получаю сообщение об ошибке "Не удается связать несколько параметров с содержимым запроса". Надеюсь, что это приводит людей сюда, которые получили ту же ошибку.
Джош Но
1
@Josh Ты использовал [FromUri]? public IEnumerable <Category> GetCategories ([FromUri] int [] categoryids) {...}
Ануп Каттел
2
@FrankGorman Нет, я не был, который был моей проблемой.
Джош Ноу

Ответы:

619

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

GetCategories([FromUri] int[] categoryIds)

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

/Categories?categoryids=1&categoryids=2&categoryids=3 
Lavel
источник
18
Что если я не знаю, сколько у меня переменных в массиве? Что если это будет 1000? Запрос не должен быть таким.
Сахар Ч.
7
Это дает мне ошибку «Элемент с таким же ключом уже был добавлен». Однако он принимает categoryids [0] = 1 & categoryids [1] = 2 & etc ...
Доктор Джонс
19
Это должен быть принятый ответ - @Hemanshu Bhojak: не пора ли выбрать?
Дэвид Реттенбахер
12
Причина этого заключается в следующем заявлении на веб-сайте ASP.NET Web API, в котором говорится о привязке параметра: «Если параметр имеет« простой »тип, Web API пытается получить значение из URI. Простые типы включают. Примитивные типы NET (int, bool, double и т. Д.), Плюс TimeSpan, DateTime, Guid, decimal и string, а также любой тип с преобразователем типов, который может преобразовывать строки. " int [] не является простым типом.
Tr1stan
3
Это хорошо работает для меня. Один пункт. В коде сервера параметр массива должен стоять первым, чтобы он работал, а после него - любые другие параметры. При вводе параметров в запросе порядок не важен.
Искра
102

Как указывает Филип 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массив.

Mrchief
источник
10
Это может нарушать SRP и / или SoC, но вы можете легко сделать так, чтобы это также наследовалось, ModelBinderAttributeпоэтому его можно было использовать напрямую вместо трудоемкого синтаксиса с использованием typeof()аргумента. Все , что вам нужно сделать , это унаследовать так: CommaDelimitedArrayModelBinder : ModelBinderAttribute, IModelBinderа затем предоставить конструктор по умолчанию , который выталкивает определение типа до базового класса: public CommaDelimitedArrayModelBinder() : base(typeof(CommaDelimitedArrayModelBinder)) { }.
sliderhouserules
В противном случае мне действительно нравится это решение, и я использую его в своем проекте, так что ... спасибо. :)
sliderhouserules
Напомним, что это решение не работает с генериками, такими System.Collections.Generic.List<long>как bindingContext.ModelType.GetElementType()только System.Arrayтипы поддержки
ViRuSTriNiTy
@ViRuSTriNiTy: Этот вопрос и ответ конкретно говорят о массивах. Если вам нужно общее решение на основе списка, это довольно тривиально для реализации. Не стесняйтесь задавать отдельный вопрос, если вы не знаете, как это сделать.
Mrchief
2
@codeMonkey: размещение массива в теле имеет смысл для запроса POST, но как быть с запросами GET? У них обычно нет содержимого в теле.
stakx - больше не участвует
40

Я недавно столкнулся с этим требованием сам, и я решил внедрить, 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', а не 'ids', так как это указано в моем маршруте):

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

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

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

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

Стив Четти
источник
1
Тип int жестко (int.Parse) в вашем решении. Imho, решение @ Mrchief лучше
razon
27

В случае, если кому-то понадобится - добиться того же или похожего (например, удалить) с помощью 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 будет ожидать, что тело сообщения будет содержать значение в кодировке urlen, как =1&=2&=3без имени параметра и без скобок.

Sofija
источник
2
Не нужно прибегать к ПОЧТЕ. Смотрите ответ @Lavel.
Андре Верланг
3
Существует ограничение на количество данных, которые вы можете отправить в URI. И по стандарту это не должен быть запрос GET, так как он фактически изменяет данные.
Достойно7
1
И где именно ты увидел GET здесь? :)
София
3
@Sofija OP говорит code to retrieve categories from database, что метод должен быть методом GET, а не POST.
Азимут
22

Простой способ отправить массив параметров в веб-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

Джинеш Вария
источник
3
чем это отличается от принятого ответа? за исключением реализации запроса ajax через jquery, который не имеет ничего общего с оригинальным постом.
Сксаллай
13

Вы можете попробовать этот код, чтобы получить значения, разделенные запятыми / массив значений, чтобы получить 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"} 
]
Навин Виджай
источник
12

Решение 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.

Викторио Берра
источник
Я думаю, что рекомендация MS, о которой вы говорите, удовлетворена этим ответом: stackoverflow.com/a/49563970/4367683
Machado
Ты это видел? github.com/aspnet/Mvc/pull/7967 выглядит так, как будто они добавили исправление для начала анализа List <what> в строке запроса без необходимости специального связывателя. Кроме того, ссылка, на которую вы ссылаетесь, не является ASPNET Core, и я не думаю, что она поможет в моей ситуации.
Викторио Берра
Лучший, не хакерский ответ.
Эрик Филипс
7

Вместо использования пользовательского 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.

PhillipM
источник
Не понимаю, почему это решение не набрало много голосов. Это красиво и чисто и работает с чванством без добавления пользовательских переплетов и прочего.
Тим
Лучший / самый чистый ответ на мой взгляд. Спасибо PhillipM!
Ли Боуэрс
7

Первоначально я использовал решение, которое @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]
crabCRUSHERclamCOLLECTOR
источник
1
В случае, если кому-то еще нужна информация о библиотеках, которые он использует. Вот использование для "CommaDelimitedArrayParameterBinder". using System.Collections.Generic; использование System.Linq; используя System.Threading; использование System.Threading.Tasks; использование System.Web.Http.Controllers; using System.Web.Http.Metadata; using System.Web.Http.ModelBinding; using System.Web.Http.ValueProviders; using System.Web.Http.ValueProviders.Providers;
SteckDEV
6
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
Waninlezu
источник
@ Эльза Не могли бы вы указать, какой кусок вы не можете понять? Я думаю, что код вполне понятен для объяснения сам. Мне сложно объяснить все это по-английски, извините.
Waninlezu
@ Стив Czetty вот моя реконструированная версия, спасибо за вашу идею
Waninlezu
Будет ли он работать /в качестве сепаратора? Тогда вы можете иметь: dns / root / mystuff / path / to / some / resource, сопоставленный сpublic string GetMyStuff(params string[] pathBits)
RoboJ1M
5

Если вы хотите получить список / массив целых чисел, то самый простой способ сделать это - принять список строк, разделенных запятыми (,), и преобразовать его в список целых чисел. Не забудьте упомянуть [FromUri] attriubte.your url выглядит так:

...? ID = 71 & ACCOUNTID = 1,2,3,289,56

public HttpResponseMessage test([FromUri]int ID, [FromUri]string accountID)
{
    List<int> accountIdList = new List<int>();
    string[] arrAccountId = accountId.Split(new char[] { ',' });
    for (var i = 0; i < arrAccountId.Length; i++)
    {
        try
        {
           accountIdList.Add(Int32.Parse(arrAccountId[i]));
        }
        catch (Exception)
        {
        }
    }
}
Vaibhav
источник
почему вы используете List<string>вместо просто string? в нем будет только одна строка, которая есть 1,2,3,289,56в вашем примере. Я предложу изменить.
Даниэль Тулп
Работал на меня. Я был удивлен, что мой контроллер не будет связываться с List<Guid>автоматически, хотя. Обратите внимание, в Asp.net Core аннотация есть [FromQuery], и она не нужна.
kitsu.eb
2
Для однострочной версии Linq: int [] accountIdArray = accountId.Split (','). Select (i => int.Parse (i)). ToArray (); Я бы избежал подвоха, так как он замаскирует кого-то, передающего неверные данные.
Стив в CO
3

Сделайте метод типа [HttpPost], создайте модель с одним параметром int [] и отправьте сообщение с помощью json:

/* Model */
public class CategoryRequestModel 
{
    public int[] Categories { get; set; }
}

/* WebApi */
[HttpPost]
public HttpResponseMessage GetCategories(CategoryRequestModel model)
{
    HttpResponseMessage resp = null;

    try
    {
        var categories = //your code to get categories

        resp = Request.CreateResponse(HttpStatusCode.OK, categories);

    }
    catch(Exception ex)
    {
        resp = Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
    }

    return resp;
}

/* jQuery */
var ajaxSettings = {
    type: 'POST',
    url: '/Categories',
    data: JSON.serialize({Categories: [1,2,3,4]}),
    contentType: 'application/json',
    success: function(data, textStatus, jqXHR)
    {
        //get categories from data
    }
};

$.ajax(ajaxSettings);
codeMonkey
источник
Вы оборачиваете свой массив в класс - это будет работать нормально (несмотря на MVC / WebAPI). ОП был о привязке к массиву без класса-оболочки.
Mrchief
1
Первоначальная проблема ничего не говорит о том, как сделать это без класса-оболочки, просто они хотели использовать параметры запроса для сложных объектов. Если вы зайдете по этому пути слишком далеко, вы попадете в точку, где вам понадобится API, чтобы выбрать действительно сложный объект js, и запрос параметров потерпит неудачу. Можно также научиться делать это так, как это будет работать каждый раз.
codeMonkey
public IEnumerable<Category> GetCategories(int[] categoryIds){- да, вы могли бы интерпретировать по-разному, я полагаю. Но часто я не хочу создавать классы-оболочки для создания оболочек. Если у вас есть сложные объекты, то это будет просто работать. Поддержка этих простых случаев - это то, что не работает "из коробки", следовательно, OP.
Mrchief
3
Делать это через POST- на самом деле против парадигмы REST. Таким образом, такой API не был бы REST API.
Азимут
1
@Azimuth, дайте мне парадигму, с одной стороны, что работает с .NET, с другой
codeMonkey,
3

Или вы можете просто передать строку разделенных элементов и поместить ее в массив или список на принимающей стороне.

Sirentec
источник
2

Я решил эту проблему следующим образом.

Я использовал сообщение в API для отправки списка целых чисел в качестве данных.

Затем я вернул данные как бесчисленные.

Код отправки выглядит следующим образом:

public override IEnumerable<Contact> Fill(IEnumerable<int> ids)
{
    IEnumerable<Contact> result = null;
    if (ids!=null&&ids.Count()>0)
    {
        try
        {
            using (var client = new HttpClient())
            {
                client.BaseAddress = new Uri("http://localhost:49520/");
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

                String _endPoint = "api/" + typeof(Contact).Name + "/ListArray";

                HttpResponseMessage response = client.PostAsJsonAsync<IEnumerable<int>>(_endPoint, ids).Result;
                response.EnsureSuccessStatusCode();
                if (response.IsSuccessStatusCode)
                {
                    result = JsonConvert.DeserializeObject<IEnumerable<Contact>>(response.Content.ReadAsStringAsync().Result);
                }

            }

        }
        catch (Exception)
        {

        }
    }
    return result;
}

Код получения выглядит следующим образом:

// POST api/<controller>
[HttpPost]
[ActionName("ListArray")]
public IEnumerable<Contact> Post([FromBody]IEnumerable<int> ids)
{
    IEnumerable<Contact> result = null;
    if (ids != null && ids.Count() > 0)
    {
        return contactRepository.Fill(ids);
    }
    return result;
}

Это работает просто отлично для одной записи или нескольких записей. Заполнение является перегруженным методом, использующим DapperExtensions:

public override IEnumerable<Contact> Fill(IEnumerable<int> ids)
{
    IEnumerable<Contact> result = null;
    if (ids != null && ids.Count() > 0)
    {
        using (IDbConnection dbConnection = ConnectionProvider.OpenConnection())
        {
            dbConnection.Open();
            var predicate = Predicates.Field<Contact>(f => f.id, Operator.Eq, ids);
            result = dbConnection.GetList<Contact>(predicate);
            dbConnection.Close();
        }
    }
    return result;
}

Это позволяет вам извлекать данные из составной таблицы (список идентификаторов), а затем возвращать записи, которые вас действительно интересуют, из целевой таблицы.

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

Кроме того, детали того, что вы ищете из базы данных, не отображаются в строке запроса. Вам также не нужно конвертировать из CSV-файла.

При использовании любого инструмента, такого как интерфейс web api 2.x, следует помнить, что функции get, put, post, delete, head и т. Д. Имеют общее назначение, но не ограничиваются этим использованием.

Таким образом, хотя публикация обычно используется в контексте создания в интерфейсе веб-API, она не ограничивается этим использованием. Это обычный вызов html, который может использоваться для любых целей, разрешенных практикой html.

Кроме того, детали того, что происходит, скрыты от тех «любопытных глаз», которые мы так много слышим о наших днях.

Гибкость в соглашениях об именах в интерфейсе web api 2.x и использование обычных веб-вызовов означает, что вы отправляете вызов в web api, который вводит в заблуждение шумеров, заставляя их думать, что вы действительно делаете что-то еще. Например, вы можете использовать «POST» для реального извлечения данных.

Тимоти Дулинг
источник
2

Я создал специальный механизм связывания, который преобразует любые значения, разделенные запятыми (только примитивные, десятичные, с плавающей точкой, строки), в соответствующие им массивы.

public class CommaSeparatedToArrayBinder<T> : IModelBinder
    {
        public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
        {
            Type type = typeof(T);
            if (type.IsPrimitive || type == typeof(Decimal) || type == typeof(String) || type == typeof(float))
            {
                ValueProviderResult val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
                if (val == null) return false;

                string key = val.RawValue as string;
                if (key == null) { bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Wrong value type"); return false; }

                string[] values = key.Split(',');
                IEnumerable<T> result = this.ConvertToDesiredList(values).ToArray();
                bindingContext.Model = result;
                return true;
            }

            bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Only primitive, decimal, string and float data types are allowed...");
            return false;
        }

        private IEnumerable<T> ConvertToDesiredArray(string[] values)
        {
            foreach (string value in values)
            {
                var val = (T)Convert.ChangeType(value, typeof(T));
                yield return val;
            }
        }
    }

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

 public IHttpActionResult Get([ModelBinder(BinderType = typeof(CommaSeparatedToArrayBinder<int>))] int[] ids)
        {
            return Ok(ids);
        }
Сулабх Сингла
источник
Спасибо, я портировал его на netcore 3.1 без особых усилий, и он работает! Принятый ответ не решает проблему с необходимостью указывать имя параметра много-много раз и аналогичен операции по умолчанию в netcore 3.1
Богдан Март,
0

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

Вот как вы используете:

public class MustBeListAndContainAttribute : ValidationAttribute
{
    private Regex regex = null;
    public bool RemoveDuplicates { get; }
    public string Separator { get; }
    public int MinimumItems { get; }
    public int MaximumItems { get; }

    public MustBeListAndContainAttribute(string regexEachItem,
        int minimumItems = 1,
        int maximumItems = 0,
        string separator = ",",
        bool removeDuplicates = false) : base()
    {
        this.MinimumItems = minimumItems;
        this.MaximumItems = maximumItems;
        this.Separator = separator;
        this.RemoveDuplicates = removeDuplicates;

        if (!string.IsNullOrEmpty(regexEachItem))
            regex = new Regex(regexEachItem, RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase);
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var listOfdValues = (value as List<string>)?[0];

        if (string.IsNullOrWhiteSpace(listOfdValues))
        {
            if (MinimumItems > 0)
                return new ValidationResult(this.ErrorMessage);
            else
                return null;
        };

        var list = new List<string>();

        list.AddRange(listOfdValues.Split(new[] { Separator }, System.StringSplitOptions.RemoveEmptyEntries));

        if (RemoveDuplicates) list = list.Distinct().ToList();

        var prop = validationContext.ObjectType.GetProperty(validationContext.MemberName);
        prop.SetValue(validationContext.ObjectInstance, list);
        value = list;

        if (regex != null)
            if (list.Any(c => string.IsNullOrWhiteSpace(c) || !regex.IsMatch(c)))
                return new ValidationResult(this.ErrorMessage);

        return null;
    }
}
Алан Кардосо
источник