Передать массив целых чисел в ASP.NET веб-API?
у меня есть ASP.NET Web API (версия 4) REST service, где мне нужно передать массив целых чисел.
вот мой метод действий:
public IEnumerable<Category> GetCategories(int[] categoryIds){
// code to retrieve categories from database
}
и это URL, который я пробовал:
/Categories?categoryids=1,2,3,4
15 ответов:
вам просто нужно добавить
[FromUri]перед параметром, выглядит так:GetCategories([FromUri] int[] categoryIds)и отправить запрос:
/Categories?categoryids=1&categoryids=2&categoryids=3
как Филип W указывает, что вам, возможно, придется прибегнуть к пользовательской модели binder, как это (изменено для привязки к фактическому типу param):
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 веб-API правильно свяжет вашcategoryIdsмассив.
Я недавно столкнулся с этим требованием сам, и я решил реализовать
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возможно, вам придется рефакторинг это для удовлетворения ваших конкретных потребностей.
простой способ отправки параметров массива в web api
API
public IEnumerable<Category> GetCategories([FromUri]int[] categoryIds){ // code to retrieve categories from database }Jquery: отправить объект JSON как запрос params
$.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
в случае, если кому - то понадобится-добиться того же или подобного(например, удалить) через
POSTвместоFromUriиспользуйтеFromBodyи на стороне клиента(JS/jQuery) формат param как$.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без имени параметра, и без скобок.
вы можете попробовать этот код для вас, чтобы взять значения через запятую / массив значений, чтобы вернуть 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"} ]
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
Если вы хотите перечислить/ массив целых чисел самый простой способ сделать это-принять разделенный запятыми (,) список строк и преобразовать его в список integers.Do не забыл упомянуть [FromUri] attriubte.Ваш 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) { } } }
сделайте тип метода [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);
Я изначально использовал решение, которое @Mrchief в течение многих лет (он отлично работает). Но когда, когда я добавил чванство для моего проекта для документации 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 указал мне в правильном направлении
- Суэггер только не смог выбрать мою запятую конечные точки при использовании атрибута [Route]
вместо использования пользовательского 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.
ASP.NET решение Core 2.0 (Swagger Ready)
Input
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.
я решил эту проблему таким образом.
я использовал сообщение post для api, чтобы отправить список целых чисел в качестве данных.
затем я вернул данные как ienumerable.
код отправки выглядит следующим образом:
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-файла.
вы должны иметь в виду при использовании любого инструмента, как веб-API 2.X интерфейс заключается в том, что get, put, post, delete, head и т. д., функции имеют общее использование, но не ограничиваются этим использованием.
таким образом, хотя post обычно используется в контексте создания в интерфейсе веб-api, он не ограничивается этим использованием. Это обычный вызов html, который может быть использован для любых целей, разрешенных практикой html.
В гибкость в соглашениях об именах в веб-api 2.X интерфейс и использование регулярных веб-вызовов означает, что вы отправляете вызов веб-api, который вводит в заблуждение ищеек, думая, что вы действительно делаете что-то еще. Например, вы можете использовать "POST" для реального извлечения данных.
или вы можете просто передать строку элементов с разделителями и поместить ее в массив или список на принимающей стороне.
мое решение состояло в том, чтобы создать атрибут для проверки строк, он делает кучу дополнительных общих функций, включая проверку регулярных выражений, которые вы можете использовать для проверки только чисел, а затем позже я конвертирую в целые числа по мере необходимости...
Как использовать:
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; } }
Comments