您的问题引起了我的兴趣,因此我想提出一个比 Nkosi 提供的答案更通用的解决方案。虽然 Nkosi 的回答会起作用,但我不喜欢 ModelBinder 语法以及为每种类型定义一个新的 ModelBinder。我以前一直在玩 ParameterBindingAttribute 并且非常喜欢语法,所以我想从这个开始。这允许您定义类似 [FromUri] 或 [FromBody] 的语法。我还希望能够使用不同的“数组”类型,例如 int[] 或 List 或 HashSet 或最好的 IEnumerable。
第 1 步:创建 HttpParameterBinding
第 2 步:创建 ParameterBindingAttribute
第 3 步:将所有内容放在一起
HttpParameterBinding 允许您解析任何 RouteData 并通过设置 actionContext 的 ActionArguments 字典将它们传递给您的方法。您只需从 HttpParameterBinding 继承并覆盖 ExecuteBindingAsync 方法。如果你愿意,你可以在这里抛出异常,但你也可以让它流过,如果它无法解析 RouteData,该方法将收到 null。对于此示例,我正在创建一个由 RouteData 组成的数组的 JSON 字符串。由于我们知道 Json.NET 在解析数据类型方面非常出色,因此使用它似乎很自然。这将解析 RouteData 以获取 CSV 值。这最适用于整数或日期。
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);
}
}
ParameterBindingAttribute 允许我们使用在方法签名中声明绑定权的简洁语法。我决定我想使用 [FromUriCsv] 作为语法,以便适当地命名该类。唯一需要覆盖的是 GetBinding 方法,我们在该方法中连接刚刚创建的 CsvParameterBinding 类。
using System.Web.Http;
using System.Web.Http.Controllers;
public class FromUriCsvAttribute : ParameterBindingAttribute
{
public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
{
return new CsvParameterBinding(parameter);
}
}
现在把它放在控制器上并使用它。
[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
}
我真的很喜欢它的转变方式。它对于整数和日期非常有效,但对于小数则失败了,因为路径中的句点确实将其提升了。目前,这很好地解决了您的问题。如果需要十进制数字,则应该能够使用 regex 或 mvc 样式路由调整路由。我使用了与此类似的方法,以便从与 [FromHeader("headerName")] 语法非常配合的标头值中提取复杂类型。