将数组传递给 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的主要内容,如果未能解决你的问题,请参考以下文章

通过 JSON 对象将 Session 对象中的字节数组传递给 Web 服务(Asp.Net 2.0 asmx)

将数组传递到 ASP.NET Core 路由查询字符串

将模型传递给 ASP.NET Core MVC 中的视图

如何在 ASP.NET Web API 实现中将数组传递给 OData 函数?

如何将文件中的其他参数从 Angular 传递给 ASP.NET Core 控制器?

将 C# ASP.NET 数组传递给 Javascript 数组