¿Pasar una matriz de enteros a ASP.NET Web API?

Tengo un servicio REST ASP.NET Web API (versión 4) donde necesito pasar una matriz de enteros.

Aquí está mi método de acción:

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

Y esta es la URL que he probado:

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

Solo tiene que agregar [FromUri] antes del parámetro, se ve así:

 GetCategories([FromUri] int[] categoryIds) 

Y enviar solicitud:

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

Como Filip W señala, es posible que tenga que recurrir a un encuadernador de modelo personalizado como este (modificado para vincularse al tipo real de param):

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

Y luego puedes decir:

/Categories?categoryids=1,2,3,4 y ASP.NET Web API vinculará correctamente su matriz categoryIds .

Recientemente descubrí este requisito y decidí implementar un ActionFilter para manejarlo.

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

Lo estoy aplicando así (tenga en cuenta que utilicé ‘id’, no ‘ids’, ya que así es como se especifica en mi ruta):

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

Y la url pública sería:

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

Es posible que tenga que refactorizar esto para satisfacer sus necesidades específicas.

Una forma fácil de enviar parámetros de matriz a la API web

API

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

Jquery: envíe el objeto JSON como parámetros de solicitud

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

../api/categories/GetCategories?categoryIds=1&categoryIds=2&categoryIds=3&categoryIds=4 su URL de solicitud como ../api/categories/GetCategories?categoryIds=1&categoryIds=2&categoryIds=3&categoryIds=4

En caso de que alguien necesite – para lograr lo mismo o algo similar (como eliminar) a través de POST lugar de FromUri , use FromBody y en el lado del cliente (JS / jQuery) param como $.param({ '': categoryids }, true)

do#:

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

jQuery:

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

La cosa con $.param({ '': categoryids }, true) es que .net esperará que el cuerpo del post contenga el valor urlencoded like =1&=2&=3 sin el nombre del parámetro, y sin corchetes.

Puede probar este código para tomar valores separados por comas / una matriz de valores para recuperar un JSON de 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; } } 

Salida:

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

Solicitar uri

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

Si desea listar / matriz de enteros de la manera más fácil de hacer esto, acepte la coma (,) lista de cadenas separadas y conviértala en una lista de enteros. No olvide mencionar [FromUri] attriubte.your url like like:

…? 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) { } } } 

Cree el método tipo [HttpPost], cree un modelo que tenga un parámetro int [] y publique con 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); 

Originalmente usé la solución que @Mrchief por años (funciona muy bien). Pero cuando agregué Swagger a mi proyecto para la documentación de API, mi punto final NO aparecía.

Me tomó un tiempo, pero esto es lo que se me ocurrió. Funciona con Swagger, y las firmas de tu método API se ven más limpias:

Al final puedes hacer:

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

Crea una nueva clase: 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() }; } 

Crea una nueva clase: StringToIntArrayConverter.cs

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

Notas:

Solución ASP.NET Core 2.0 (lista Swagger)

Entrada

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

Código

Escriba el proveedor (cómo MVC sabe qué carpeta 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; } } 

Escriba la carpeta actual (acceda a todo tipo de información sobre la solicitud, acción, modelos, tipos, lo que sea)

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

Registrarse en MVC

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

Uso de muestra con un controlador bien 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); 

EDITAR: Microsoft recomienda utilizar un TypeConverter para estos niños de operaciones con este enfoque. Por lo tanto, siga los consejos a continuación de los carteles y documente su tipo personalizado con un SchemaFilter.

Abordé este tema de esta manera.

Utilicé un mensaje de correo a la API para enviar la lista de enteros como datos.

Luego devolví los datos como un ienumerable.

El código de envío es el siguiente:

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

El código de recepción es el siguiente:

 // 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 bien para un registro o muchos registros. El relleno es un método sobrecargado que utiliza 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; } 

Esto le permite buscar datos de una tabla compuesta (la lista de id) y luego devolver los registros que realmente le interesan de la tabla de destino.

Podría hacer lo mismo con una vista, pero esto le da un poco más de control y flexibilidad.

Además, los detalles de lo que está buscando de la base de datos no se muestran en la cadena de consulta. Tampoco es necesario convertir un archivo csv.

Debe tener en cuenta que al utilizar cualquier herramienta como la interfaz web api 2.x es que las funciones get, put, post, delete, head, etc. tienen un uso general, pero no están restringidas a ese uso.

Por lo tanto, aunque la publicación se usa generalmente en un contexto de creación en la interfaz de API web, no está restringido a ese uso. Es una llamada html regular que puede usarse para cualquier propósito permitido por la práctica html.

Además, los detalles de lo que está sucediendo están ocultos a esos “ojos curiosos” de los que escuchamos tanto en estos días.

La flexibilidad en las convenciones de nomenclatura en la interfaz web api 2.x y el uso de llamadas regulares a la web significa que usted envía una llamada a la API web que engaña a los fisgonadores y les hace pensar que realmente están haciendo otra cosa. Puede usar “POST” para realmente recuperar datos, por ejemplo.

O simplemente podría pasar una cadena de elementos delimitados y ponerlo en una matriz o lista en el extremo receptor.

En lugar de utilizar un ModelBinder personalizado, también puede usar un tipo personalizado con un 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); } } 

La ventaja es que hace que los parámetros del método Web API sean muy simples. Ni siquiera necesita especificar [FromUri].

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

Este ejemplo es para una Lista de cadenas, pero podría hacer categoryIds.Select(int.Parse) o simplemente escribir una IntList en su lugar.

Mi solución fue crear un atributo para validar cadenas, tiene varias características más comunes, incluida la validación de expresiones regulares que puedes usar para verificar únicamente los números y luego los convierto en enteros según sea necesario …

Esta es la forma de usar:

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