将整数数组传递给ASP.NET Web API?

Hem*_*jak 397 c# arrays rest asp.net-web-api

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

这是我的行动方法:

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

这是我尝试过的URL:

/Categories?categoryids=1,2,3,4
Run Code Online (Sandbox Code Playgroud)

小智 578

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

GetCategories([FromUri] int[] categoryIds)
Run Code Online (Sandbox Code Playgroud)

并发送请求:

/Categories?categoryids=1&categoryids=2&categoryids=3 
Run Code Online (Sandbox Code Playgroud)

  • 这应该是公认的答案 - @Hemanshu Bhojak:不是时候接你的选择吗? (19认同)
  • 如果我不知道数组中有多少变量怎么办?怎么样就像1000?请求不应该那样. (17认同)
  • 这是因为[ASP.NET Web API网站上讨论参数绑定的以下声明:](http://www.asp.net/web-api/overview/formats-and-model-binding/ parameter-binding-in-aspnet-web-api)"如果参数是"简单"类型,Web API会尝试从URI中获取值.简单类型包括.NET基元类型(int,bool,double和所以,加上TimeSpan,DateTime,Guid,decimal和string,以及任何可以从字符串转换的类型转换器的类型."**int []不是一个简单的类型.** (11认同)
  • 这给了我一个错误"已经添加了一个具有相同键的项目.".但它确实接受categoryids [0] = 1&categoryids [1] = 2&etc ... (7认同)
  • 这对我很有用.一点.在服务器代码上,数组参数必须首先使其工作,然后是任何其他参数.在请求中输入参数时,顺序并不重要. (3认同)

Mrc*_*ief 95

正如Filip W指出的那样,您可能不得不求助于这样的自定义模型绑定器(已修改为绑定到实际类型的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以说:

/Categories?categoryids=1,2,3,4和ASP.NET Web API将正确绑定您的categoryIds阵列.

  • 这可能违反了SRP和/或SoC,但您可以轻松地将其继承自`ModelBinderAttribute`,因此可以使用`typeof()`参数直接使用它而不是费力的语法.所有你需要做的就是继承像这样:`CommaDelimitedArrayModelBinder:ModelBinderAttribute,IModelBinder`然后提供推类型定义下到基类的默认构造函数:'公共CommaDelimitedArrayModelBinder():基地(typeof运算(CommaDelimitedArrayModelBinder)){}` . (9认同)
  • @codeMonkey:将数组放入正文对POST请求很有意义,但GET请求呢?这些通常没有身体内容. (2认同)

Ste*_*tty 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; }
}
Run Code Online (Sandbox Code Playgroud)

我正在应用它(注意我使用'id',而不是'ids',因为这是我在路线中指定的方式):

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

公共网址将是:

/api/Data/1;2;3;4
Run Code Online (Sandbox Code Playgroud)

您可能需要重构这些以满足您的特定需求.


Sof*_*ija 25

如果有人需要 - 通过POST代替FromUri,使用FromBody和客户端(JS/jQuery)格式参数来实现相同或类似的事情(如删除)$.param({ '': categoryids }, true)

C#:

public IHttpActionResult Remove([FromBody] int[] categoryIds)
Run Code Online (Sandbox Code Playgroud)

jQuery的:

$.ajax({
        type: 'POST',
        data: $.param({ '': categoryids }, true),
        url: url,
//...
});
Run Code Online (Sandbox Code Playgroud)

事情$.param({ '': categoryids }, true)是它.net将期望post body包含urlencoded值,如=1&=2&=3没有参数名称,没有括号.

  • 您可以在URI中发送的数据量有限制.按照标准,这不应该是GET请求,因为它实际上是在修改数据. (3认同)
  • @Sofija OP说`用于从数据库中检索类别的代码',因此该方法应该是GET方法,而不是POST. (3认同)
  • 无需求助于POST.见@Lavel答案. (2认同)
  • 你到底是在哪里看到 GET 的?:) (2认同)

Jig*_*iya 20

将数组参数发送到web api的简便方法

API

public IEnumerable<Category> GetCategories([FromUri]int[] categoryIds){
 // code to retrieve categories from database
}
Run Code Online (Sandbox Code Playgroud)

Jquery:将JSON对象作为请求参数发送

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

它会生成您的请求网址 ../api/categories/GetCategories?categoryIds=1&categoryIds=2&categoryIds=3&categoryIds=4

  • 这与接受的答案有何不同?除了通过 jquery 实现 ajax 请求,这与原始帖子无关。 (7认同)

Nav*_*jay 10

您可以尝试使用此代码来获取逗号分隔值/值数组以从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; }
 } 
Run Code Online (Sandbox Code Playgroud)

输出:

[
{"CategoryID":"4","CategoryName":"Category_4"}, 
{"CategoryID":"5","CategoryName":"Category_5"}, 
{"CategoryID":"3","CategoryName":"Category_3"} 
]
Run Code Online (Sandbox Code Playgroud)


cra*_*TOR 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);
    }
Run Code Online (Sandbox Code Playgroud)

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

    }
}
Run Code Online (Sandbox Code Playgroud)

创建一个新类: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() };
}
Run Code Online (Sandbox Code Playgroud)

创建一个新类:StringToIntArrayConverter.cs

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

笔记:

  • /sf/answers/3298677581/指出了我正确的方向
  • 使用[Route]属性时,Swagger只能选择逗号分隔的端点

  • 万一其他人需要有关此使用的库的信息。这是“CommaDelimitedArrayParameterBinder”的使用。使用 System.Collections.Generic; 使用 System.Linq;使用 System.Threading; 使用 System.Threading.Tasks; 使用 System.Web.Http.Controllers; 使用 System.Web.Http.Metadata; 使用 System.Web.Http.ModelBinding; 使用 System.Web.Http.ValueProviders; 使用 System.Web.Http.ValueProviders.Providers; (2认同)

Vic*_*rra 7

ASP.NET Core 2.0解决方案(可快速使用)

输入项

DELETE /api/items/1,2
DELETE /api/items/1
Run Code Online (Sandbox Code Playgroud)

编写提供程序(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;
    }
}
Run Code Online (Sandbox Code Playgroud)

编写实际的资料夹(访问有关请求,操作,模型,类型等的各种信息)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

向MVC注册

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

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);
Run Code Online (Sandbox Code Playgroud)

编辑:Microsoft 建议通过这种方法对这些操作的孩子使用TypeConverter。因此,请遵循以下海报建议,并使用SchemaFilter记录您的自定义类型。


Wan*_*ezu 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));
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

    [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);
    }
Run Code Online (Sandbox Code Playgroud)

请求uri

http://localhost/api/v1/files/2a9937c7-8201-59b7-bc8d-11a9178895d0/tags/BBA5CD5D-F07D-47A9-8DEE-D19F5FA65F63,BBA5CD5D-F07D-47A9-8DEE-D19F5FA65F63
Run Code Online (Sandbox Code Playgroud)


小智 6

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

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;
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

以及如何在控制器中使用:

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


小智 5

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

...?ID = 71&帐户ID = 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)
        {
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 对于单行Linq版本:int [] accountIdArray = accountId.Split(',').选择(i => int.Parse(i)).ToArray(); 我会避免捕获,因为它会掩盖某些传递错误数据的人. (2认同)

Phi*_*ipM 5

除了使用自定义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);
    }
}
Run Code Online (Sandbox Code Playgroud)

优点是它使Web API方法的参数非常简单。您甚至不需要指定[FromUri]。

public IEnumerable<Category> GetCategories(StrList categoryIds) {
  // code to retrieve categories from database
}
Run Code Online (Sandbox Code Playgroud)

此示例用于字符串列表,但您可以这样做,也可以categoryIds.Select(int.Parse)直接编写一个IntList。