【问题标题】:Restful Web Api parameter as an array of intRestful Web Api 参数作为 int 数组
【发布时间】:2016-08-24 08:29:45
【问题描述】:

理想情况下,我希望有以下格式的 URL:

/api/categories/1,2,3...N/products

这将返回指定类别的所有产品。使用多个类别 ID 调用一次 API 可以节省多次数据库调用,从而提高性能。

我可以通过以下方式轻松实现。

public HttpResponseMessage GetProducts(string categoryIdsCsv)
{
    // <1> Split and parse categoryIdsCsv
    // <2> Get products
}

但是,这看起来不是一个干净整洁的解决方案,并且可能违反了 SRP 原则。我也尝试过使用ModelBinder,但是它向查询字符串添加了参数。

问题:

  1. 有没有一种简洁的方法来实现这种 URL 结构?
  2. 或者是否有不同/更好的方法来检索多个类别的所有产品?

如果您需要任何进一步的说明,请告诉我。

【问题讨论】:

  • /api/products?categories[]=1&amp;categories[]=2&amp;... 有什么问题?这是完成此类事情的标准方式。此外,Web API 2 会自动将这些查询参数解析为控制器方法参数int[] categories,因此无需自定义ModelBinder。

标签: c# rest asp.net-web-api2


【解决方案1】:

我刚刚找到了我的问题的答案。 Route 属性在使用ModelBinder 时缺少参数。

[Route("api/categories/{categoryIds}/products")]
public HttpResponseMessage GetProducts([ModelBinder(typeof(CategoryIdsModelBinder))] CategoryIds categoryIds)
{
    // <2> Get products using categoryIds.Ids
}

CategoryIds 会是

public class CategoryIds
{
    public List<int> Ids{ get; set; }
}

CategoryIdsModelBinder 会是

public class CategoryIdsModelBinder : IModelBinder
    {
        public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
        {
            if (bindingContext.ModelType != typeof(CategoryIds))
            {
                return false;
            }

            var val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
            if (val == null)
            {
                return false;
            }

            var key = val.RawValue as string;
            if (key == null)
            {
                bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Wrong value type");
                return false;
            }

            var values = val.AttemptedValue.Split(',');
            var ids = new List<int>();
            foreach (var value in values)
            {
                int intValue;
                int.TryParse(value.Trim(), out intValue);
                if (intValue > 0)
                {
                    ids.Add(intValue);
                }
            }

            if (ids.Count > 0)
            {
                var result = new CategoryIds
                {
                    Ids= ids
                };

                bindingContext.Model = result;
                return true;
            }

            bindingContext.ModelState.AddModelError(
                bindingContext.ModelName, "Cannot convert value to Location");
            return false;
        }

【讨论】:

    【解决方案2】:

    我们可以使用 Post 方法

    [RoutePrefix ("api/categories")] 公共类TestController { [HttpPost] [路线(“getProducts”)]

        public HttpResponseMessage GetProducts ( HttpRequestMessage request )
        {
            HttpResponseMessage  message               = null;
            string               input                 = string.Empty;
    
            input  = request.Content.ReadAsStringAsync ().Result;
             var ids = Newtonsoft.Json.JsonConvert.DeserializeObject<List<string>> ( input );
    
        }
    }
    

    【讨论】:

    • 谢谢,但我更喜欢使用GET 来保持 API 的宁静。我需要获取数据(只读)。
    • POST 不用于检索数据。根据 Http 规范,POST 方法用于在服务器上创建新资源的操作,并且该新资源将具有新的唯一 ID。 GET 方法是当您在服务器上执行数据查询时,不会改变该查询的任何资源的状态
    【解决方案3】:

    很遗憾,Web API 无法将您的数据解析为数组或某种开箱即用的自定义对象。

    如果您想将您的 url 参数解析为数组,您可以尝试这样做:

    1. 编写您自己的路由约束,它将读取您的参数并将其从字符串转换为整数/字符串/其他数组;

    2. 编写您的自定义类型转换器并将其与您的数据模型一起使用;

    3. 编写您的价值提供者并将其与您的数据模型一起使用

    4. 使用参数绑定

    此外,您始终可以使用永远不会违反 REST 原则的查询参数 :)

    请查看有关here 和here 的更多详细信息

    希望有帮助

    【讨论】:

    • 谢谢!我自己刚刚发布了一个解决方案,并找到了第二个解决方案——使用 ActionFilterAttribute 我什至可以使用 List ids 作为控制器中的参数。
    • 小心这个属性——它将在每个请求/响应流上执行
    猜你喜欢
    • 2018-06-17
    • 1970-01-01
    • 2017-04-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-23
    • 1970-01-01
    • 1970-01-01
    • 2012-07-20
    相关资源
    最近更新 更多