Asp.net web api 将整数数组传递给action方法的Web api
我有以下web api方法: [HttpGet] [RouteWorkPlanList/{clientsId}/{date:datetime}] 公共异步任务工作列表[FromUri]列表客户端SID,[FromUri]日期时间日期 { } 下面是我用来调用上述操作方法的URI:Asp.net web api 将整数数组传递给action方法的Web api,asp.net-web-api,asp.net-web-api2,Asp.net Web Api,Asp.net Web Api2,我有以下web api方法: [HttpGet] [RouteWorkPlanList/{clientsId}/{date:datetime}] 公共异步任务工作列表[FromUri]列表客户端SID,[FromUri]日期时间日期 { } 下面是我用来调用上述操作方法的URI: http://localhost/blabla/api/workPlan/WorkPlanList/5,4/2016-06-01 我在曲线括号上设置了断点,看到日期时间值完全通过,而clientsId值为0 知道我为
http://localhost/blabla/api/workPlan/WorkPlanList/5,4/2016-06-01
我在曲线括号上设置了断点,看到日期时间值完全通过,而clientsId值为0
知道我为什么在clientsId上得到0吗?在clientsId上得到0是因为框架无法将示例中的值4,5绑定到列表。在这种情况下,您将使用自定义模型绑定器,将值解析为所需的类型,并将其绑定到操作参数:
[路线定义标签A/api/工作计划]
公共类工作控制器:ApiController{
[HttpGet]
[RouteWorkPlanList/{clientsId}/{date:datetime}]
公共IHttpActionResult工作列表[ModelBindertypeofClientsIdBinder]列表客户端SID,[FromUri]日期时间日期{
var result=new{clientsId,date};
返回结果;
}
}
公共类ClientsIdBinder:IModelBinder{
public bool BindModelSystem.Web.Http.Controllers.HttpActionContext actionContext,ModelBindingContext bindingContext{
if!typeofIEnumerable.IsAssignableFrombindingContext.ModelType{
返回false;
}
var val=bindingContext.ValueProvider.GetValuebindingContext.ModelName;
如果val==null{
返回false;
}
var id=val.RawValue作为字符串;
如果id==null{
返回false;
}
var tokens=ids.Splitnew[]{',},StringSplitOptions.RemoveEmptyEntries;
如果令牌.Length>0{
var clientsId=tokens.Selects=>int.Parses;
如果bindingContext.ModelType.IsArray{
bindingContext.Model=clientsId.ToArray;
}否则{
bindingContext.Model=clientsId.ToList;
}
返回true;
}
bindingContext.ModelState.AddModelError
bindingContext.ModelName,无法转换客户端ID;
返回false;
}
}
参考:由于框架无法将示例中的值4,5绑定到列表,因此在clientsId上得到0。在这种情况下,您将使用自定义模型绑定器,将值解析为所需的类型,并将其绑定到操作参数:
[路线定义标签A/api/工作计划]
公共类工作控制器:ApiController{
[HttpGet]
[RouteWorkPlanList/{clientsId}/{date:datetime}]
公共IHttpActionResult工作列表[ModelBindertypeofClientsIdBinder]列表客户端SID,[FromUri]日期时间日期{
var result=new{clientsId,date};
返回结果;
}
}
公共类ClientsIdBinder:IModelBinder{
public bool BindModelSystem.Web.Http.Controllers.HttpActionContext actionContext,ModelBindingContext bindingContext{
if!typeofIEnumerable.IsAssignableFrombindingContext.ModelType{
返回false;
}
var val=bindingContext.ValueProvider.GetValuebindingContext.ModelName;
如果val==null{
返回false;
}
var id=val.RawValue作为字符串;
如果id==null{
返回false;
}
var tokens=ids.Splitnew[]{',},StringSplitOptions.RemoveEmptyEntries;
如果令牌.Length>0{
var clientsId=tokens.Selects=>int.Parses;
如果bindingContext.ModelType.IsArray{
bindingContext.Model=clientsId.ToArray;
}否则{
bindingContext.Model=clientsId.ToList;
}
返回true;
}
bindingContext.ModelState.AddModelError
bindingContext.ModelName,无法转换客户端ID;
返回false;
}
}
参考:自定义模型绑定是一个选项。但是在请求体中传递值比在URI中传递值更容易 作为最佳实践,复杂数据不应该出现在URI中。因此,您的解决方案是: 创建JSON数组并将其包含在请求正文中 在List clientsId之前写入[FromBody],这将强制框架从请求主体检索数据。模型绑定将自动进行
自定义模型绑定是一种选择。但是在请求体中传递值比在URI中传递值更容易 作为最佳实践,复杂数据不应该出现在URI中。因此,您的解决方案是: 创建JSON数组并将其包含在请求正文中 在List clientsId之前写入[FromBody],这将强制框架从请求主体检索数据。模型绑定将自动进行
你的问题引起了我的兴趣,所以我想提出一个比Nkosi提供的答案更通用的解决方案。虽然Nkosi的答案会起作用,但我不喜欢ModelBinder语法,也不喜欢为每种类型定义一个新的ModelBinder。我一直在 在之前使用ParameterBindingAttribute时,我非常喜欢它的语法,所以我想从这里开始。这允许您定义类似[FromUri]或[FromBody]的语法。我还希望能够使用不同的数组类型,比如int[]或List或HashSet,或者最好的是IEnumerable 步骤1:创建HttpParameterBinding 步骤2:创建ParameterBindingAttribute 第三步:把它们放在一起 HttpParameterBinding允许您解析任何RoutedData,并通过设置actionContext的ActionArguments字典将它们传递给Method。您只需从HttpParameterBinding继承并重写ExecuteBindingAsync方法。如果需要,可以在这里抛出异常,但也可以让它通过,如果该方法无法解析RouteData,则该方法将接收null。在本例中,我创建了一个JSON字符串,该字符串是由RouteData生成的数组。因为我们知道Json.NET在解析数据类型方面非常出色,所以使用它似乎很自然。这将为CSV值解析RoutedData。这最适用于整数或日期
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Metadata;
using Newtonsoft.Json;
public class CsvParameterBinding : HttpParameterBinding
{
public CsvParameterBinding(HttpParameterDescriptor descriptor) : base(descriptor)
{
}
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
{
var paramName = this.Descriptor.ParameterName;
var rawParamemterValue = actionContext.ControllerContext.RouteData.Values[paramName].ToString();
var rawValues = rawParamemterValue.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
//To convert the raw value int a true JSON array we need to make sure everything is quoted.
var jsonString = $"[\"{string.Join("\",\"", rawValues)}\"]";
try
{
var obj = JsonConvert.DeserializeObject(jsonString, this.Descriptor.ParameterType);
actionContext.ActionArguments[paramName] = obj;
}
catch
{
//There was an error casting, the jsonString must be invalid.
//Don't set anything and the action will just receive null.
}
return Task.FromResult<object>(null);
}
}
现在把它放在控制器上并使用它
[Route("WorkPlanList/{clientsId}/{date:datetime}")]
public async Task<IHttpActionResult> WorkPlanList([FromUriCsv] List<int> clientsId, [FromUri] DateTime date)
{
//matches WorkPlanList/2,3,4/7-3-2016
}
[Route("WorkPlanList/{clientsId}")]
public async Task<IHttpActionResult> WorkPlanList([FromUriCsv] HashSet<int> clientsId)
{
//matches WorkPlanList/2,3,4,5,2
//clientsId will only contain 2,3,4,5 since it's a HashSet the extra 2 won't be included.
}
[Route("WorkPlanList/{clientsId}/{dates}")]
public async Task<IHttpActionResult> WorkPlanList([FromUriCsv] IEnumerable<int> clientsId, [FromUriCsv] IEnumerable<DateTime> dates)
{
//matches WorkPlanList/2,3,4/5-2-16,6-17-16,7-3-2016
}
我真的很喜欢这一切。它对整数和日期非常有效,但对小数无效,因为路径中的周期确实会将其顶起。现在,这很好地解决了您的问题。如果需要十进制数,则可以使用正则表达式或mvc样式的路由调整路由。我使用了与此类似的方法,以便从标题值中提取复杂类型,这在[FromHeaderheaderName]语法中非常有效。您的问题引起了我的兴趣,因此我想提出一个比Nkosi提供的答案更通用的解决方案。虽然Nkosi的答案会起作用,但我不喜欢ModelBinder语法,也不喜欢为每种类型定义一个新的ModelBinder。我以前一直在玩ParameterBindingAttribute,非常喜欢它的语法,所以我想从这个开始。这允许您定义类似[FromUri]或[FromBody]的语法。我还希望能够使用不同的数组类型,比如int[]或List或HashSet,或者最好的是IEnumerable 步骤1:创建HttpParameterBinding 步骤2:创建ParameterBindingAttribute 第三步:把它们放在一起 HttpParameterBinding允许您解析任何RoutedData,并通过设置actionContext的ActionArguments字典将它们传递给Method。您只需从HttpParameterBinding继承并重写ExecuteBindingAsync方法。如果需要,可以在这里抛出异常,但也可以让它通过,如果该方法无法解析RouteData,则该方法将接收null。在本例中,我创建了一个JSON字符串,该字符串是由RouteData生成的数组。因为我们知道Json.NET在解析数据类型方面非常出色,所以使用它似乎很自然。这将为CSV值解析RoutedData。这最适用于整数或日期
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Metadata;
using Newtonsoft.Json;
public class CsvParameterBinding : HttpParameterBinding
{
public CsvParameterBinding(HttpParameterDescriptor descriptor) : base(descriptor)
{
}
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
{
var paramName = this.Descriptor.ParameterName;
var rawParamemterValue = actionContext.ControllerContext.RouteData.Values[paramName].ToString();
var rawValues = rawParamemterValue.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
//To convert the raw value int a true JSON array we need to make sure everything is quoted.
var jsonString = $"[\"{string.Join("\",\"", rawValues)}\"]";
try
{
var obj = JsonConvert.DeserializeObject(jsonString, this.Descriptor.ParameterType);
actionContext.ActionArguments[paramName] = obj;
}
catch
{
//There was an error casting, the jsonString must be invalid.
//Don't set anything and the action will just receive null.
}
return Task.FromResult<object>(null);
}
}
现在把它放在控制器上并使用它
[Route("WorkPlanList/{clientsId}/{date:datetime}")]
public async Task<IHttpActionResult> WorkPlanList([FromUriCsv] List<int> clientsId, [FromUri] DateTime date)
{
//matches WorkPlanList/2,3,4/7-3-2016
}
[Route("WorkPlanList/{clientsId}")]
public async Task<IHttpActionResult> WorkPlanList([FromUriCsv] HashSet<int> clientsId)
{
//matches WorkPlanList/2,3,4,5,2
//clientsId will only contain 2,3,4,5 since it's a HashSet the extra 2 won't be included.
}
[Route("WorkPlanList/{clientsId}/{dates}")]
public async Task<IHttpActionResult> WorkPlanList([FromUriCsv] IEnumerable<int> clientsId, [FromUriCsv] IEnumerable<DateTime> dates)
{
//matches WorkPlanList/2,3,4/5-2-16,6-17-16,7-3-2016
}
我真的很喜欢这一切。它对整数和日期非常有效,但对小数无效,因为路径中的周期确实会将其顶起。现在,这很好地解决了您的问题。如果需要十进制数,则可以使用正则表达式或mvc样式的路由调整路由。我使用了与此类似的方法,以便从标题值中提取复杂类型,这种方法非常适合[FromHeaderheaderName]语法。尝试以编辑方式提交@ManOVision的答案,但单独显示可能更合适 在实现他的答案时,我发现它不支持可选的绑定参数。我已经做了一些更新来支持这一点,如下所示 我在未传递参数时收到的错误是:
{
"Message": "The request is invalid.",
"MessageDetail": "The parameters dictionary does not contain an entry for parameter 'skus' of type 'System.String[]' for method 'System.Web.Http.IHttpActionResult Get(System.String[], System.String, System.String, System.String, System.String, Boolean)' in 'eGAPI.Controllers.GiftCardsController'. The dictionary must contain an entry for each parameter, including parameters that have null values."
}
实施:
[Route("{skus}")]
public IHttpActionResult Get([FromUriCsv] string[] skus = null)
更新代码:
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
{
var paramName = Descriptor.ParameterName;
try
{
if (actionContext.ControllerContext.RouteData.Values.ContainsKey(paramName))
{
var rawParamemterValue = actionContext.ControllerContext.RouteData.Values[paramName]?.ToString();
if (!string.IsNullOrEmpty(rawParamemterValue))
{
var rawValues = rawParamemterValue.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
actionContext.ActionArguments[paramName] = JsonConvert.DeserializeObject($"[\"{string.Join("\",\"", rawValues)}\"]", Descriptor.ParameterType);
}
else
{
actionContext.ActionArguments[paramName] = null;
}
}
else
{
actionContext.ActionArguments[paramName] = null;
}
}
catch (Exception)
{
actionContext.ActionArguments[paramName] = null;
}
return Task.FromResult<object>(null);
}
尝试将@ManOVision的答案作为编辑提交,但单独显示可能更合适 在实现他的答案时,我发现它不支持可选的绑定参数。我已经做了一些更新来支持这一点,如下所示 我在未传递参数时收到的错误是:
{
"Message": "The request is invalid.",
"MessageDetail": "The parameters dictionary does not contain an entry for parameter 'skus' of type 'System.String[]' for method 'System.Web.Http.IHttpActionResult Get(System.String[], System.String, System.String, System.String, System.String, Boolean)' in 'eGAPI.Controllers.GiftCardsController'. The dictionary must contain an entry for each parameter, including parameters that have null values."
}
实施:
[Route("{skus}")]
public IHttpActionResult Get([FromUriCsv] string[] skus = null)
更新代码:
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
{
var paramName = Descriptor.ParameterName;
try
{
if (actionContext.ControllerContext.RouteData.Values.ContainsKey(paramName))
{
var rawParamemterValue = actionContext.ControllerContext.RouteData.Values[paramName]?.ToString();
if (!string.IsNullOrEmpty(rawParamemterValue))
{
var rawValues = rawParamemterValue.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
actionContext.ActionArguments[paramName] = JsonConvert.DeserializeObject($"[\"{string.Join("\",\"", rawValues)}\"]", Descriptor.ParameterType);
}
else
{
actionContext.ActionArguments[paramName] = null;
}
}
else
{
actionContext.ActionArguments[paramName] = null;
}
}
catch (Exception)
{
actionContext.ActionArguments[paramName] = null;
}
return Task.FromResult<object>(null);
}
此方法不适用于GET请求,因为请求正文不应包含有关GET请求的任何数据。此方法适用于POST,但在将新信息发布到OP的端点时会丢失RESTful特性。此方法不适用于GET请求,因为请求正文不应包含GET请求的任何数据。这种方法非常适合POST,但是当您将新信息发布到OP的端点时,就会失去RESTful特性。