【问题标题】:Pass an array of integers to ASP.NET Web API?将整数数组传递给 ASP.NET Web API?
【发布时间】:2012-04-16 09:39:51
【问题描述】:

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

这是我的操作方法:

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

这是我尝试过的网址:

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

【问题讨论】:

  • 在使用“/Categories?categoryids=1&categoryids=2&categoryids=3”之类的查询字符串时出现“无法将多个参数绑定到请求的内容”错误。希望这会将遇到同样错误的人带到这里。
  • @Josh 你用过 [FromUri] 吗?公共 IEnumerable GetCategories([FromUri] int[] categoryids){...}
  • @FrankGorman 不,我不是,这是我的问题。

标签: c# arrays rest asp.net-web-api


【解决方案1】:

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

GetCategories([FromUri] int[] categoryIds)

并发送请求:

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

【讨论】:

  • 如果我不知道数组中有多少变量怎么办?如果是1000呢?请求不应该是这样的。
  • 这给了我一个错误“已经添加了具有相同密钥的项目。”。但是它确实接受 categoryids[0]=1&categoryids[1]=2& 等...
  • 这应该是公认的答案 - @Hemanshu Bhojak:现在是时候选择了吗?
  • 这个原因是由于ASP.NET Web API website talking about parameter binding:中的以下语句“如果参数是“简单”类型,Web API 会尝试从 URI 中获取值。简单类型包括 . NET 基本类型(int、bool、double 等),加上 TimeSpan、DateTime、Guid、decimal 和 string,以及任何具有可以从字符串转换的类型转换器的类型。” int[] 不是简单类型。
  • 这对我很有效。一点。在服务器代码上,数组参数必须首先出现才能工作,然后是任何其他参数。在请求中输入参数时,顺序并不重要。
【解决方案2】:

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

【讨论】:

  • 这可能违反 SRP 和/或 SoC,但您可以轻松地使其也继承自 ModelBinderAttribute,因此可以直接使用它,而不是使用 typeof() 参数的繁琐语法。您所要做的就是像这样继承:CommaDelimitedArrayModelBinder : ModelBinderAttribute, IModelBinder,然后提供一个将类型定义下推到基类的默认构造函数:public CommaDelimitedArrayModelBinder() : base(typeof(CommaDelimitedArrayModelBinder)) { }
  • 否则,我真的很喜欢这个解决方案,并且正在我的项目中使用它,所以...谢谢。 :)
  • 附注,此解决方案不适用于 System.Collections.Generic.List&lt;long&gt; 等泛型,因为 bindingContext.ModelType.GetElementType() 仅支持 System.Array 类型
  • @ViRuSTriNiTy:这个问题和答案专门讨论了数组。如果您需要基于通用列表的解决方案,那么实现起来相当简单。如果您不确定如何解决,请随时提出单独的问题。
  • @codeMonkey:将数组放入正文中对于 POST 请求很有意义,但是 GET 请求呢?这些通常在正文中没有内容。
【解决方案3】:

我最近自己也遇到了这个需求,我决定实现一个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

您可能需要对其进行重构以满足您的特定需求。

【讨论】:

  • type int 在您的解决方案中被硬编码 (int.Parse)。恕我直言,@Mrchief 的解决方案更好
【解决方案4】:

如果有人需要 - 通过POST 而不是FromUri 实现相同或相似的事情(如删除),请使用FromBody 并在客户端(JS/jQuery)格式参数为$.param({ '': categoryids }, true)

c#:

public IHttpActionResult Remove([FromBody] int[] categoryIds)

jQuery:

$.ajax({
        type: 'POST',
        data: $.param({ '': categoryids }, true),
        url: url,
//...
});

$.param({ '': categoryids }, true) 的问题在于,.net 将期望帖子正文包含像 =1&amp;=2&amp;=3 这样的 urlencoded 值,不带参数名称,也不带括号。

【讨论】:

  • 无需使用 POST。请参阅@Lavel 答案。
  • 在 URI 中可以发送的数据量是有限制的。按照标准,这不应该是 GET 请求,因为它实际上是在修改数据。
  • 您在哪里看到了 GET? :)
  • @Sofija OP 说 code to retrieve categories from database,因此该方法应该是 GET 方法,而不是 POST。
  • @Azimuth 但在这个答案/示例中,它是 Remove 方法,而不是检索方法。
【解决方案5】:

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

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

【讨论】:

  • 这与接受的答案有何不同?除了通过 jquery 实现与原始帖子无关的 ajax 请求。
【解决方案6】:

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

【讨论】:

    【解决方案7】:

    ASP.NET Core 2.0 解决方案(Swagger Ready)

    输入

    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 recommends using a TypeConverter for these kids of operations 采用这种方法。因此,请遵循以下海报建议并使用 SchemaFilter 记录您的自定义类型。

    【讨论】:

    • 我认为您所说的 MS 推荐对这个答案很满意:stackoverflow.com/a/49563970/4367683
    • 你看到了吗? github.com/aspnet/Mvc/pull/7967 看起来好像他们添加了一个修复程序来开始解析查询字符串中的 List 而不需要特殊的活页夹。此外,您链接的帖子不是 ASPNET Core,我认为对我的情况没有帮助。
    • 最好的,非hacky的答案。
    【解决方案8】:

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

    【讨论】:

    • 不明白为什么这个解决方案没有得到很多选票。它既漂亮又干净,可以在不添加自定义活页夹和其他东西的情况下使用 swagger。
    • 我认为最好/最干净的答案。谢谢菲利普!
    • 现在 (2020/2021) 很简单 [HttpGet] public string GetMany([FromQuery] List id) { return "something"; }
    • 请注意,Swagger 将整数数组视为字符串。它可以工作,但如果 Swagger 将其视为整数数组会更好。
    • @Thieme,我认为答案得到的票数较少,因为它需要使用自定义类型而不是使用标准类型(即 int 数组)一般来说,您的 API 应该对所有方法,因此注册自定义活页夹不是问题。
    【解决方案9】:

    我最初使用 @Mrchief 多年的解决方案(效果很好)。但是,当我将 Swagger 添加到我的 API 文档项目中时,我的终点是 NOT 出现。

    我花了一段时间,但这是我想出的。它适用于 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);
        }
    }
    

    注意事项:

    【讨论】:

    • 以防万一其他人需要有关此使用的库的信息。这是“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;
    【解决方案10】:
    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 你能指出哪一段你看不懂吗?我认为代码很清楚,可以自行解释。我很难用英语解释这一切,对不起。
    • @Steve Czetty 这是我的重构版本,谢谢你的想法
    • 它可以使用/ 作为分隔符吗?那么您可以:dns/root/mystuff/path/to/some/resource 映射到 public string GetMyStuff(params string[] pathBits)
    【解决方案11】:

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

    ...?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&lt;string&gt; 而不是string?它里面只有一个字符串,在你的例子中是1,2,3,289,56。我会建议编辑。
    • 为我工作。我很惊讶我的控制器不会自动绑定到List&lt;Guid&gt;。注意 Asp.net Core 中的注解是[FromQuery],不需要。
    • 对于单行 Linq 版本: int[] accountIdArray = accountId.Split(',').Select(i => int.Parse(i)).ToArray();我会避免捕获,因为它会掩盖传递错误数据的人。
    【解决方案12】:

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

    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 中的默认操作相同
    【解决方案13】:

    将方法类型设为[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 是关于在没有包装类的情况下绑定到数组。
    • 最初的问题并没有说明没有包装类的情况,只是他们想对复杂对象使用查询参数。如果您在这条路上走得太远,您将需要 API 来获取一个非常复杂的 js 对象,并且查询参数将使您失败。不妨学习以每次都能奏效的方式去做。
    • public IEnumerable&lt;Category&gt; GetCategories(int[] categoryIds){ - 是的,我想你可以用不同的方式解释。但是很多时候,我不想为了创建包装器而创建包装器类。如果您有复杂的对象,那么这将起作用。支持这些更简单的案例是不能开箱即用的,因此是 OP。
    • 通过POST 这样做实际上违反了 REST 范式。因此这样的 API 不会是 REST API。
    • @Azimuth 一方面给我一个范式,另一方面与 .NET 一起工作
    【解决方案14】:

    或者您可以只传递一串分隔项并将其放入接收端的数组或列表中。

    【讨论】:

      【解决方案15】:

      我是这样解决这个问题的。

      我使用 api 的 post 消息将整数列表作为数据发送。

      然后我将数据作为 ienumerable 返回。

      发送代码如下:

      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等函数有通用用途,但不限于该用途.

      因此,虽然 post 通常用于 web api 接口中的创建上下文,但它并不限于该用途。这是一个常规 html 调用,可用于 html 实践允许的任何目的。

      此外,我们现在经常听到的那些“窥探的眼睛”隐藏了正在发生的事情的细节。

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

      【讨论】:

        【解决方案16】:

        我的解决方案是创建一个属性来验证字符串,它提供了许多额外的通用功能,包括正则表达式验证,您可以使用它来仅检查数字,然后根据需要转换为整数...

        这是你的使用方式:

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

        【讨论】:

          【解决方案17】:

          我刚刚在请求的属性中添加了 Query 键(Refit lib)。

          [查询(CollectionFormat.Multi)]

          public class ExampleRequest
          {
                 
                  [FromQuery(Name = "name")]
                  public string Name { get; set; }               
                 
                  [AliasAs("category")]
                  [Query(CollectionFormat.Multi)]
                  public List<string> Categories { get; set; }
          }
          

          【讨论】:

          • 此解决方案的请求是什么样的?这是否适用于 Swagger Docs?
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-12-31
          • 2013-07-20
          • 2020-06-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多