将数组传递给 asp net core web api 操作方法 HttpGet

Posted

技术标签:

【中文标题】将数组传递给 asp net core web api 操作方法 HttpGet【英文标题】:passing an array to a asp net core web api action method HttpGet 【发布时间】:2018-12-20 09:39:59 【问题描述】:

我正在尝试向我的操作方法发送一个整数数组,代码如下所示:

[HttpGet]
    public async Task<IActionResult> ServicesByCategoryIds([FromQuery] int[] ids)
    
        var services = await _accountsUow.GetServiceProfilesByCategoryIdsAsync(ids);
        return Ok(services);
    

我这样调用方法:https://localhost:44343/api/accounts/servicesbycategoryids?ids=1&ids=2

但是当我调用这个方法时总是得到一个空数组,即使我在查询字符串中传递了 id。我正在使用 .net 核心 2.1。

我在谷歌上搜索的所有内容都表明这实际上就是这样做的方式。 . . 我在这里有什么遗漏吗?

谢谢!

【问题讨论】:

这能回答你的问题吗? Pass an array of integers to ASP.NET Web API? 【参考方案1】:

答案是简单地用[FromQuery] 属性修饰数组就可以使绑定工作。如果没有该属性,它将无法绑定。就是这样,上面@kennyzx 的答案是最好的,但我觉得需要像这样简单地说明这一点:[FromQuery] 就是你所需要的。我不知道为什么这些其他答案会选择ModelBinder 路线,这可能是某些场景所需要的,但就我而言,我确信与其他许多人一样,关键是不要忘记应用[FromQuery] 属性.

public ActionResult GetFoo(int id, [FromQuery] Guid[] someIds)  ... 

【讨论】:

【参考方案2】:

与 Plamen 的答案略有不同。

数组似乎有一个空的GenericTypeArguments 所以添加了GetElementType() 重命名类以避免与框架类ArrayModelBinder冲突。 根据需要添加了对元素类型的检查。 更多用括号括住数组的选项。
public class CustomArrayModelBinder : IModelBinder

    public Task BindModelAsync(ModelBindingContext bindingContext)
    
        if (!bindingContext.ModelMetadata.IsEnumerableType)
        
            bindingContext.Result = ModelBindingResult.Failed();
            return Task.CompletedTask;
        

        var value = bindingContext.ValueProvider
            .GetValue(bindingContext.ModelName)
            .ToString();

        if (string.IsNullOrWhiteSpace(value))
        
            bindingContext.Result = ModelBindingResult.Success(null);
            return Task.CompletedTask;
        

        var elementType = bindingContext.ModelType.GetElementType() ??
            bindingContext.ModelType.GetTypeInfo().GenericTypeArguments.FirstOrDefault();

        if (elementType == null)
        
            bindingContext.Result = ModelBindingResult.Failed();
            return Task.CompletedTask;
        

        var converter = TypeDescriptor.GetConverter(elementType);

        var values = value.Split(',', StringSplitOptions.RemoveEmptyEntries)
            .Select(x => converter.ConvertFromString(Clean(x)))
            .ToArray();

        var typedValues = Array.CreateInstance(elementType, values.Length);
        values.CopyTo(typedValues, 0);
        bindingContext.Model = typedValues;

        bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
        return Task.CompletedTask;
    

    private static string Clean(string str)
    
        return str.Trim('(', ')').Trim('[', ']').Trim();
    

然后使用IEnumerable&lt;T&gt;IList&lt;T&gt; 或数组T[]

[ModelBinder(BinderType = typeof(CustomArrayModelBinder))] IEnumerable<T> ids
                                                       ... T[] ids
                                                       ... IList<T> ids

参数可以在路径或带有可选括号的查询中。

[Route("resources/ids")]

resource/ids/1,2,3
resource/ids/(1,2,3)
resource/ids/[1,2,3]

[Route("resources")]

resource?ids=1,2,3
resource?ids=(1,2,3)
resource?ids=[1,2,3]

【讨论】:

var elementType = bindingContext.ModelType.GetElementType();在您的 CustomModelArrayBinder 中将仅适用于 Array 类型。所以 IEnumerable 和 List 将不起作用。这可能很好,对我来说效果很好。我认为应该修改您的答案以删除其他选项。 @iGanja 更新以支持 IEnumerable、IList 如果“ids”参数是可选的,或者如果查询字符串还传递了一些其他参数,那么这个带有 CustomArrayModelBinder 的解决方案是否也可以工作? @RickyTad 没有测试那个场景,但它应该可以工作。如果您有问题,请尝试并反馈。【参考方案3】:

您可以将自定义模型绑定器和 id 实现为 URI 的一部分,而不是在查询字符串中。

您的端点可能如下所示: /api/accounts/servicesbycategoryids/(1,2)

public class ArrayModelBinder : IModelBinder

    public Task BindModelAsync(ModelBindingContext bindingContext)
    
        // Our binder works only on enumerable types
        if (!bindingContext.ModelMetadata.IsEnumerableType)
        
            bindingContext.Result = ModelBindingResult.Failed();
            return Task.CompletedTask;
        

        // Get the inputted value through the value provider
        var value = bindingContext.ValueProvider
            .GetValue(bindingContext.ModelName).ToString();

        // If that value is null or whitespace, we return null
        if (string.IsNullOrWhiteSpace(value))
        
            bindingContext.Result = ModelBindingResult.Success(null);
            return Task.CompletedTask;
        

        // The value isn't null or whitespace,
        // and the type of the model is enumerable.
        // Get the enumerable's type, and a converter
        var elementType = bindingContext.ModelType.GetTypeInfo().GenericTypeArguments[0];
        var converter = TypeDescriptor.GetConverter(elementType);

        // Convert each item in the value list to the enumerable type
        var values = value.Split(new[]  "," , StringSplitOptions.RemoveEmptyEntries)
            .Select(x => converter.ConvertFromString(x.Trim()))
            .ToArray();

        // Create an array of that type, and set it as the Model value
        var typedValues = Array.CreateInstance(elementType, values.Length);
        values.CopyTo(typedValues, 0);
        bindingContext.Model = typedValues;

        // return a successful result, passing in the Model
        bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
        return Task.CompletedTask;
    

然后在你的行动中使用它:

[HttpGet("(ids)", Name="GetAuthorCollection")]
public IActionResult GetAuthorCollection(
    [ModelBinder(BinderType = typeof(ArrayModelBinder))] IEnumerable<int> ids)

    //enter code here

从一门复数课程中学到这一点:使用 ASP.NET Core 构建 RESTful API

【讨论】:

我强烈推荐kevin dockx课程!【参考方案4】:

您可以使用 [FromBody] 代替 [ab] 使用查询字符串(考虑 1000 个 ID),并将 ID 列表作为 JSON 数组传递:

public IActionResult ServicesByCategoryIds([FromBody] int[] ids)

只要涉及到 OpenAPI/Swagger,就会生成适当的规范:

    "parameters": [
      
        "name": "ids",
        "in": "body",
        "required": true,
        "schema": 
          "type": "array",
          "items": 
            "type": "integer",
            "format": "int32"
          
        
      
    ],

【讨论】:

如您所见,您需要使用正确的 Content-Type 将 HTTP 动词更改为 HttpPost,因为现在您正在通过 Body 传递参数。该问题与 HttpGet 动词有关【参考方案5】:

Array 参数绑定失败是Asp.Net Core 2.1 下的一个已知问题,已记录在Array or List in query string does not get parsed #7712。

对于临时解决方法,您可以设置FromQuery Name Property,如下所示:

        [HttpGet()]
    [Route("ServicesByCategoryIds")]
    public async Task<IActionResult> ServicesByCategoryIds([FromQuery(Name = "ids")]int[] ids)
                
        return Ok();
    

【讨论】:

当我按照您链接的讨论中的建议删除 ApiControllerAttribute 时,似乎也可以工作。 帮助很大!!谢谢 推荐使用下面来自 yshehab 或 Plamen Yovchev 的更新答案【参考方案6】:

我创建了一个新的 web api 类,只有一个操作。

[Produces("application/json")]
[Route("api/accounts")]
public class AccountsController : Controller

    [HttpGet]
    [Route("servicesbycategoryids")]
    public IActionResult ServicesByCategoryIds([FromQuery] int[] ids)
    
        return Ok();
    

然后使用与您相同的网址:

http://localhost:2443/api/accounts/servicesbycategoryids?ids=1&ids=2

它正在工作。

【讨论】:

以上是关于将数组传递给 asp net core web api 操作方法 HttpGet的主要内容,如果未能解决你的问题,请参考以下文章