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


427

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

这是我的操作方法:

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

这是我尝试过的URL:

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

1
使用“ / Categories?categoryids = 1&categoryids = 2&categoryids = 3”之类的查询字符串时,出现“无法将多个参数绑定到请求的内容”错误。希望这会把遇到同样错误的人带到这里。
2014年

1
@Josh您是否使用过[FromUri]?公开IEnumerable <类别> GetCategories([FromUri] int [] categoryids){...}
Anup Kattel

2
@FrankGorman不,我不是,这是我的问题。
Josh Noe

Answers:


619

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

GetCategories([FromUri] int[] categoryIds)

并发送请求:

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

18
如果我不知道数组中有多少变量怎么办?如果是1000怎么办?该请求不应该那样。
Sahar Ch。

7
这给我一个错误“具有相同密钥的项目已被添加。”。但是,它确实接受categoryids [0] = 1&categoryids [1] = 2&等等...
Jones Jones医生

19
这应该是一个可以接受的答案-@Hemanshu Bhojak:是不是该选拔时间了?
David Rettenbacher 2015年

12
这是由于ASP.NET Web API网站上有关参数绑定的以下语句引起的 “如果参数是“简单”类型,则Web API会尝试从URI获取值。简单类型包括。 NET基本类型(int,bool,double等),再加上TimeSpan,DateTime,Guid,十进制和字符串,再加上带有可以从字符串转换的类型转换器的任何类型。” int []不是简单的类型。
Tr1stan 2015年

3
这对我来说很好。一点。在服务器代码上,必须首先使用array参数,然后才能使用它。在请求中输入参数时,顺序不重要。
2016年

102

正如Filip 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,4ASP.NET Web API将正确绑定您的categoryIds数组。


10
这可能会违反SRP和/或SoC,但是您可以轻松地使其也继承自SRP和/或SoC,ModelBinderAttribute因此可以直接使用它,而不必使用使用typeof()参数的费力语法。您要做的就是像这样继承:CommaDelimitedArrayModelBinder : ModelBinderAttribute, IModelBinder,然后提供一个默认构造函数,将类型定义下推到基类:public CommaDelimitedArrayModelBinder() : base(typeof(CommaDelimitedArrayModelBinder)) { }
slidehousehouserules

否则,我真的很喜欢这个解决方案,并在我的项目中使用它,所以...谢谢。:)
Sliderhouserules

另外请注意,此解决方案不适System.Collections.Generic.List<long>用于bindingContext.ModelType.GetElementType()仅作为支持System.Array类型的泛型
ViRuSTriNiTy

@ViRuSTriNiTy:这个问题和答案专门讨论数组。如果您需要基于列表的通用解决方案,则实现起来很简单。如果您不确定该怎么做,请随时提出一个单独的问题。
Mrchief

2
@codeMonkey:将数组放入主体对于POST请求很有意义,但是GET请求呢?这些通常在体内没有含量。
stakx-不再提供

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

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

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

公开网址为:

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

您可能需要重构它以满足您的特定需求。


1
类型int在您的解决方案中进行了硬编码(int.Parse)。Imho,@ Mrchief的解决方案更好
razon 2015年

27

如果有人需要-通过POST而不是通过来实现相同或相似的事情(例如delete)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不带参数名和不带括号。


2
无需求助于POST。参见@Lavel答案。
安德烈·韦朗(AndréWerlang)

3
可以在URI中发送的数据量是有限制的。按照标准,这不应是GET请求,因为它实际上是在修改数据。
Worthy7年

1
您在哪里看到GET?:)
索非亚

3
@Sofija OP说code to retrieve categories from database,因此该方法应该是GET方法,而不是POST。
方位角

22

将数组参数发送到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
});

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


3
这与接受的答案有何不同?除了通过jquery实现ajax请求外,与原始帖子无关。
sksallaj

13

您可以尝试使用以下代码来获取逗号分隔的值/值数组,以从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"} 
]

12

ASP.NET Core 2.0解决方案(可立即使用Swagger)

输入项

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记录您的自定义类型。


我认为您正在谈论的MS建议对此答案很满意:stackoverflow.com/a/49563970/4367683
Machado

你看到了吗?github.com/aspnet/Mvc/pull/7967看起来好像他们添加了一个修复程序,无需特殊的绑定器即可开始解析查询字符串中的List <whatever>。另外,您链接的帖子不是ASPNET Core,我认为这种情况对您没有帮助。
Victorio Berra

最好的,非骇人听闻的答案。
Erik Philips

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

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

笔记:


1
万一其他人需要有关此库的信息。这是“ 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;
SteckDEV '19

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

用法:

    [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

@Elsa能否请您指出您不懂的那一部分?我认为代码很容易自我解释。对不起,我很难用英语解释这一切。
Waninlezu 2014年

@Steve Czetty这是我的重建版本,感谢您的想法
Waninlezu 2014年

它可以/用作分隔符吗?然后,您可能会:dns / root / mystuff / path / to / some / resource映射到public string GetMyStuff(params string[] pathBits)
RoboJ1M 2014年

6

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


不明白为什么这个解决方案得不到很多票。它很干净,可以轻松地使用,而无需添加自定义的活页夹和东西。
Thieme

我认为最好/最干净的答案。谢谢PhillipM!
Leigh Bowers

5

如果要列出/整数数组,最简单的方法是接受用逗号(,)分隔的字符串列表并将其转换为整数列表。不要忘了提及[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)
        {
        }
    }
}

你为什么用List<string>而不是仅仅string1,2,3,289,56在您的示例中,它将只有一个字符串。我会建议进行修改。
丹尼尔TULP

为我工作。我很惊讶我的控制器不会List<Guid>自动绑定。请注意,在Asp.net Core中,注释为[FromQuery],并且不需要。
kitsu.eb

2
对于单行Linq版本:int [] accountIdArray = accountId.Split(',')。Select(i => int.Parse(i))。ToArray(); 我会避免这种情况,因为它会掩盖某些人传递的错误数据。
史蒂夫·

3

使方法类型为[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);

您将数组包装在一个类中-可以正常工作(尽管使用MVC / WebAPI)。OP关于绑定到没有包装类的数组。
恶作剧

1
最初的问题并没有说明没有包装类的情况,只是他们想对复杂对象使用查询参数。如果您走的太远,您将需要使用API​​来拾取一个非常复杂的js对象,而查询参数将使您失败。最好学会以每次都能使用的方式进行操作。
codeMonkey

public IEnumerable<Category> GetCategories(int[] categoryIds){-是的,我想我可以用不同的方式解释。但是很多时候,我不想为了创建包装器而创建包装器类。如果您有复杂的对象,那将起作用。支持这些更简单的情况是开箱即用的,因此是OP。
Mrchief

3
通过这样做POST实际上违反了REST范式。因此,这样的API不会是REST API。
方位角

1
@Azimuth一方面给了我一个范例,另一方面又与.NET一起工作
codeMonkey

3

或者,您可以只传递一串定界项目,然后将其放入接收端的数组或列表中。


2

我以这种方式解决了这个问题。

我使用了一个发布消息到api来发送整数列表作为数据。

然后我将数据作为一个无数返回。

发送代码如下:

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

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

您可以对视图执行相同的操作,但这会给您更多的控制和灵活性。

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

使用任何工具(例如Web api 2.x界面)时,请记住,get,put,post,delete,head等功能具有一般用途,但不仅限于此用途。

因此,尽管通常在Web api界面的创建上下文中使用post,但不限于此。这是一个常规的 html调用,可以用于html实践允许的任何目的。

此外,这些天我们一直在听到的那些“狡猾的眼睛”中隐藏了正在发生的事情的细节。

Web api 2.x界面中命名约定的灵活性以及常规Web调用的使用意味着您向Web api发送了一个调用,这会误导窥探者以为您确实在做其他事情。例如,您可以使用“ POST”来真正检索数据。


2

我创建了一个自定义模型活页夹,该活页夹将任何逗号分隔的值(仅原始,十进制,浮点数,字符串)转换为它们对应的数组。

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

谢谢,我毫不费力地将其移植到了netcore 3.1上,并且可以正常工作!接受的答案不能解决需要多次指定参数名称的问题,并且与netcore 3.1中的默认操作相同
Bogdan Mart

0

我的解决方案是创建一个属性来验证字符串,它具有许多额外的常用功能,包括可用于仅检查数字的正则表达式验证,然后稍后根据需要将其转换为整数。

这是您的用法:

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;
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.