Passar uma matriz de inteiros para a API da Web do ASP.NET?

Eu tenho um serviço REST do ASP.NET Web API (versão 4) onde eu preciso passar uma matriz de inteiros.

Aqui está o meu método de ação:

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

E esta é a URL que tentei:

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

    Você só precisa adicionar [FromUri] antes do parâmetro, se parece com:

     GetCategories([FromUri] int[] categoryIds) 

    E envie pedido:

     /Categories?categoryids=1&categoryids=2&categoryids=3 

    Como Filip W aponta, você pode ter que recorrer a um fichário de modelo personalizado como este (modificado para vincular ao tipo real de parâmetro):

     public IEnumerable 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; } } 

    E então você pode dizer:

    /Categories?categoryids=1,2,3,4 e a API da Web do ASP.NET vinculará corretamente sua matriz categoryIds .

    Recentemente me deparei com esse requisito e decidi implementar um ActionFilter para lidar com isso.

     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; } } 

    Eu estou aplicando assim (note que eu usei ‘id’, não ‘ids’, pois é assim que é especificado no meu caminho):

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

    E o URL público seria:

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

    Talvez seja necessário refatorar isso para atender às suas necessidades específicas.

    Maneira fácil de enviar params matriz para web api

    API

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

    JQuery: enviar object JSON como params de solicitação

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

    Ele gerará seu URL de solicitação, como ../api/categories/GetCategories?categoryIds=1&categoryIds=2&categoryIds=3&categoryIds=4

    No caso de alguém precisar – para obter o mesmo ou similar (como excluir) via POST invés de FromUri , use FromBody e no lado do cliente (JS / jQuery) param como $.param({ '': categoryids }, true)

    c #:

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

    jQuery:

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

    A coisa com $.param({ '': categoryids }, true) é que o .net espera que o post body contenha o valor urlencoded como =1&=2&=3 sem o nome do parâmetro e sem colchetes.

    Você pode tentar esse código para obter valores separados por vírgula / uma matriz de valores para recuperar um JSON do webAPI

      public class CategoryController : ApiController { public List Get(String categoryIDs) { List categoryRepo = new List(); 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; } } 

    Saída:

     [ {"CategoryID":"4","CategoryName":"Category_4"}, {"CategoryID":"5","CategoryName":"Category_5"}, {"CategoryID":"3","CategoryName":"Category_3"} ] 
     public class ArrayInputAttribute : ActionFilterAttribute { private readonly string[] _ParameterNames; ///  /// ///  public string Separator { get; set; } ///  /// cons ///  ///  public ArrayInputAttribute(params string[] parameterName) { _ParameterNames = parameterName; Separator = ","; } ///  /// ///  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)); } } 

    Uso:

      [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); } 

    Pedido uri

     http://localhost/api/v1/files/2a9937c7-8201-59b7-bc8d-11a9178895d0/tags/BBA5CD5D-F07D-47A9-8DEE-D19F5FA65F63,BBA5CD5D-F07D-47A9-8DEE-D19F5FA65F63 

    Se você quiser listar / array de inteiros a maneira mais fácil de fazer isso é aceitar a lista separada por vírgula (,) da string e convertê-la em lista de números inteiros. Não se esqueça de mencionar a URL [FromUri] attriubte.your como:

    …? ID = 71 & accountID = 1,2,3,289,56

     public HttpResponseMessage test([FromUri]int ID, [FromUri]string accountID) { List accountIdList = new List(); string[] arrAccountId = accountId.Split(new char[] { ',' }); for (var i = 0; i < arrAccountId.Length; i++) { try { accountIdList.Add(Int32.Parse(arrAccountId[i])); } catch (Exception) { } } } 

    Faça o tipo de método [HttpPost], crie um modelo que tenha um parâmetro int [] e poste com 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); 

    Eu originalmente usei a solução que @Mrchief por anos (funciona muito bem). Mas quando adicionei Swagger ao meu projeto para documentação da API, meu ponto final NÃO estava aparecendo.

    Demorei um pouco, mas foi assim que eu cheguei. Funciona com o Swagger, e as assinaturas do seu método API parecem mais limpas:

    No final você pode fazer:

      // 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 .. } } 

    Crie uma nova class: CommaDelimitedArrayParameterBinder.cs

     public class CommaDelimitedArrayParameterBinder : HttpParameterBinding, IValueProviderParameterBinding { public CommaDelimitedArrayParameterBinder(HttpParameterDescriptor desc) : base(desc) { } ///  /// Handles Binding (Converts a comma delimited string into an array of integers) ///  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 ValueProviderFactories { get; } = new[] { new QueryStringValueProviderFactory() }; } 

    Crie uma nova class: StringToIntArrayConverter.cs

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

    Notas:

    Solução ASP.NET Core 2.0 (Swagger Ready)

    Entrada

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

    Código

    Escreva o provedor (como o MVC sabe qual fichário usar)

     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)) { return new BinderTypeModelBinder(typeof(CommaDelimitedArrayParameterBinder)); } return null; } } 

    Escreva o fichário real (acesse todos os tipos de informações sobre a solicitação, ação, modelos, tipos, etc.)

     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)) { bindingContext.Result = ModelBindingResult.Success(ints.ToList()); } return Task.CompletedTask; } } 

    Registre-o no MVC

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

    Uso de amostra com um controlador bem documentado para Swagger

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

    EDIT: Microsoft recomenda o uso de um TypeConverter para essas crianças de operações sobre essa abordagem. Portanto, siga os conselhos de pôsteres abaixo e documente seu tipo personalizado com um SchemaFilter.

    Eu abordei este assunto desta maneira.

    Eu usei uma mensagem post para a API para enviar a lista de inteiros como dados.

    Então retornei os dados como inumeráveis.

    O código de envio é o seguinte:

     public override IEnumerable Fill(IEnumerable ids) { IEnumerable 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>(_endPoint, ids).Result; response.EnsureSuccessStatusCode(); if (response.IsSuccessStatusCode) { result = JsonConvert.DeserializeObject>(response.Content.ReadAsStringAsync().Result); } } } catch (Exception) { } } return result; } 

    O código de recebimento é o seguinte:

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

    Funciona muito bem para um registro ou muitos registros. O preenchimento é um método sobrecarregado usando DapperExtensions:

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

    Isso permite buscar dados de uma tabela composta (a lista de IDs) e, em seguida, retornar os registros nos quais você está realmente interessado na tabela de destino.

    Você poderia fazer o mesmo com uma visão, mas isso lhe dá um pouco mais de controle e flexibilidade.

    Além disso, os detalhes do que você está procurando no database não são mostrados na string de consulta. Você também não precisa converter de um arquivo csv.

    Você deve ter em mente ao usar qualquer ferramenta como a interface web api 2.x que as funções get, put, post, delete, head etc. têm um uso geral, mas não estão restritas a esse uso.

    Portanto, embora a postagem seja geralmente usada em um contexto de criação na interface da API da web, ela não está restrita a esse uso. É uma chamada html regular que pode ser usada para qualquer finalidade permitida pela prática de html.

    Além disso, os detalhes do que está acontecendo estão escondidos daqueles “olhos curiosos” que ouvimos tanto nesses dias.

    A flexibilidade nas convenções de nomenclatura na interface web api 2.xe o uso de chamadas pela web normais significa que você envia uma chamada para a API da web que induz os bisbilhoteiros a pensar que você está realmente fazendo outra coisa. Você pode usar “POST” para realmente recuperar dados, por exemplo.

    Ou você pode simplesmente passar uma sequência de itens delimitados e colocá-los em uma matriz ou lista no final do recebimento.

    Em vez de usar um ModelBinder personalizado, você também pode usar um tipo personalizado com um TypeConverter.

     [TypeConverter(typeof(StrListConverter))] public class StrList : List { public StrList(IEnumerable 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); } } 

    A vantagem é que isso torna os parâmetros do método da API da Web muito simples. Você nem precisa especificar [FromUri].

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

    Este exemplo é para uma lista de strings, mas você poderia fazer categoryIds.Select(int.Parse) ou simplesmente escrever uma IntList.

    Minha solução foi criar um atributo para validar strings, ele faz um monte de resources extras comuns, incluindo validação de regex que você pode usar para verificar somente números e depois convertê-los em números inteiros conforme necessário …

    É assim que você usa:

     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)?[0]; if (string.IsNullOrWhiteSpace(listOfdValues)) { if (MinimumItems > 0) return new ValidationResult(this.ErrorMessage); else return null; }; var list = new List(); 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; } }