将一个整数数组传递给ASP。NET Web API?

我有一个ASP。NET Web API(版本4)REST服务,我需要传递一个整数数组。

下面是我的动作方法:

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

这是我试过的网址:

/Categories?categoryids=1,2,3,4
411378 次浏览

你可以尝试这段代码来获取逗号分隔的值/一个值数组来从webAPI返回JSON

 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"}
]

你只需要在参数前添加[FromUri],如下所示:

GetCategories([FromUri] int[] categoryIds)

并发送请求:

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

我自己最近遇到了这个要求,我决定实现一个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));
}

public url是:

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

您可能必须对其进行重构以满足您的特定需求。

正如菲利普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数组。

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

如果有人需要-通过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期望post正文包含urlencoded的值,如=1&=2&=3,不带参数名,不带括号。

如果你想列出/数组的整数,最简单的方法是接受逗号(,)分隔的字符串列表,并将其转换为整数列表。不要忘记提到[FromUri]属性。你的url看起来像:

…? ID = 71, accountID = 1, 2, 3289年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)
{
}
}
}

我是这样处理这个问题的。

我使用了一个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;
}

它只适用于一张唱片或多张唱片。fill是一个使用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;
}

这允许您从组合表(id列表)中获取数据,然后从目标表中返回您真正感兴趣的记录。

您也可以对视图执行相同的操作,但这为您提供了更多的控制和灵活性。

此外,查询字符串中不会显示您正在从数据库中查找的内容的详细信息。您也不必从csv文件进行转换。

当你使用像web api这样的工具时,你必须记住这一点。X接口是指get、put、post、delete、head等函数有一般用途,但不限于那种用途。

因此,虽然post通常用于web api接口中的创建上下文,但它并不仅限于此用途。它是一个常规的 html调用,可以用于html实践允许的任何目的。

此外,正在发生的事情的细节对我们这些天听到太多的“窥探的眼睛”来说是隐藏的。

web api中命名约定的灵活性2。X接口和使用常规的网络调用意味着你向web API发送了一个调用,这会误导窥探者认为你实际上在做其他事情。例如,您可以使用“POST”来真正检索数据。

将方法类型设置为[HttpPost],创建一个有一个int[]参数的模型,并使用json post:

/* 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);

简单的方法发送数组参数到web 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 < / p >

或者,您可以只传递一串分隔项,并将其放入接收端的数组或列表中。

除了使用自定义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代替。

我最初使用@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);
}
}

注:

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

编辑:微软建议为这些子操作使用TypeConverter在这个方法。因此,按照下面海报的建议,用SchemaFilter记录您的自定义类型。

我的解决方案是创建一个属性来验证字符串,它做了一堆额外的公共功能,包括regex验证,你可以使用它来检查数字,然后我根据需要转换为整数…

你可以这样使用:

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

我已经创建了一个自定义模型绑定器,它将任何逗号分隔的值(仅为原语、十进制、浮点数、字符串)转换为相应的数组。

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

以及如何在Controller中使用:

 public IHttpActionResult Get([ModelBinder(BinderType = typeof(CommaSeparatedToArrayBinder<int>))] int[] ids)
{
return Ok(ids);
}

我只是在请求的属性中添加了查询键(Refit lib)。

(查询(CollectionFormat.Multi))

public class ExampleRequest
{
       

[FromQuery(Name = "name")]
public string Name { get; set; }
       

[AliasAs("category")]
[Query(CollectionFormat.Multi)]
public List<string> Categories { get; set; }
}

所有其他解决方案都需要大量的工作。我试图在HttpGet方法参数中使用IEnumerable<long>long[],但我认为没有必要做所有的工作,只是为了使处理程序方法参数long[]的签名。我最终只是把它变成字符串,然后在处理程序中把它分开。我只说了一句。

public async Task<IActionResult> SomeHandler(string idsString)
{
var ids = idsString.Split(',').Select(x => long.Parse(x));

现在你可以传递这些数字

.../SomeHandler?idsString=123,456,789,012