ASP.NET Core 1 Web API 模型绑定数组
Posted
技术标签:
【中文标题】ASP.NET Core 1 Web API 模型绑定数组【英文标题】:ASP.NET Core 1 Web API Model Binding Array 【发布时间】:2016-08-16 03:06:36 【问题描述】:如何在 ASP.NET Core 1 Web API 中使用 GET 对 URI 中的数组进行建模(隐式或显式)?
在 ASP.NET Web API pre Core 1 中,这有效:
[HttpGet]
public void Method([FromUri] IEnumerable<int> ints) ...
您如何在 ASP.NET Web API Core 1(又名 ASP.NET 5 又名 ASP.NET vNext)中做到这一点?文档一无所有。
【问题讨论】:
嘿,@bzlm 你真的花了 100 个代表来看看我们是否正确地参数化我们的输入吗? @MarcGravell 不会起作用,无论您是否不这样做。他忘了在最后开始评论--
@MarcGravell 值得 100 个虚点 :)
【参考方案1】:
@987654321@
类结合了 @987654322@
和 @987654323@
类。根据您的路由配置/发送的请求,您应该能够将您的属性替换为其中之一。
但是,有一个可用的 shim 可以为您提供 FromUriAttribute
类。通过包资源管理器安装“Microsoft.AspNet.Mvc.WebApiCompatShim”NuGet包,或者直接添加到你的project.json文件中:
"dependencies":
"Microsoft.AspNet.Mvc.WebApiCompatShim": "6.0.0-rc1-final"
虽然有点旧,但我发现this article 很好地解释了一些变化。
绑定
如果您希望为数组绑定逗号分隔值(“/api/values?ints=1,2,3”),您将需要像以前一样的自定义绑定器。这是在 ASP.NET Core 中使用的 Mrchief's solution 的改编版本。
public class CommaDelimitedArrayModelBinder : IModelBinder
public Task BindModelAsync(ModelBindingContext bindingContext)
if (bindingContext.ModelMetadata.IsEnumerableType)
var key = bindingContext.ModelName;
var value = bindingContext.ValueProvider.GetValue(key).ToString();
if (!string.IsNullOrWhiteSpace(value))
var elementType = bindingContext.ModelType.GetTypeInfo().GenericTypeArguments[0];
var converter = TypeDescriptor.GetConverter(elementType);
var values = value.Split(new[] "," , StringSplitOptions.RemoveEmptyEntries)
.Select(x => converter.ConvertFromString(x.Trim()))
.ToArray();
var typedValues = Array.CreateInstance(elementType, values.Length);
values.CopyTo(typedValues, 0);
bindingContext.Result = ModelBindingResult.Success(typedValues);
else
// change this line to null if you prefer nulls to empty arrays
bindingContext.Result = ModelBindingResult.Success(Array.CreateInstance(bindingContext.ModelType.GetElementType(), 0));
return TaskCache.CompletedTask;
return TaskCache.CompletedTask;
您可以在Startup.cs
中指定要用于所有集合的模型绑定器:
public void ConfigureServices(IServiceCollection services)
// Add framework services.
services.AddMvc().AddMvcOptions(opts =>
opts.ModelBinders.Insert(0, new CommaDelimitedArrayModelBinder());
);
或者在您的 API 调用中指定一次:
[HttpGet]
public void Method([ModelBinder(BinderType = typeof(CommaDelimitedArrayModelBinder))] IEnumerable<int> ints)
【讨论】:
问题是关于隐式数组模型绑定。在 ASP.NET Core 1 中使用FromQuery
或 FromRoute
not 为 HttpGet
启用逗号分隔的隐式数组模型绑定(它在 ASP.NET Web API 和 ASP.NET MVC 中实现) .
@bzlm 在问题中没有提到绑定逗号分隔的数组值。你从哪里得到的?
“数组”一词在问题标题中(“模型绑定数组”),代码示例将IEnumerable<int> ints
作为操作的参数。 :) 为清楚起见,编辑了问题。你知道答案吗(是否使用FromQuery
)?
没有提到“逗号分隔”这个短语,这是我要求澄清的地方。像/api/values?ints=1&ints=2&ints3
这样的请求无需任何属性即可工作。像 /api/values?ints=1,2,3
这样的请求将需要一个自定义模型绑定器,就像以前一样。我在答案中添加了一个活页夹。
这行不通了,我试了又试了......虽然modelBinder运行了,但是控制器上的参数说它是空的【参考方案2】:
ASP.NET Core 1.1 答案
@WillRay 的回答有点过时了。我写了一个“IModelBinder”和“IModelBinderProvider”。第一个可与[ModelBinder(BinderType = typeof(DelimitedArrayModelBinder))]
属性一起使用,而第二个可用于全局应用模型绑定器,如下所示。
.AddMvc(options =>
// Add to global model binders so you don't need to use the [ModelBinder] attribute.
var arrayModelBinderProvider = options.ModelBinderProviders.OfType<ArrayModelBinderProvider>().First();
options.ModelBinderProviders.Insert(
options.ModelBinderProviders.IndexOf(arrayModelBinderProvider),
new DelimitedArrayModelBinderProvider());
)
public class DelimitedArrayModelBinderProvider : IModelBinderProvider
public IModelBinder GetBinder(ModelBinderProviderContext context)
if (context == null)
throw new ArgumentNullException(nameof(context));
if (context.Metadata.IsEnumerableType && !context.Metadata.ElementMetadata.IsComplexType)
return new DelimitedArrayModelBinder();
return null;
public class DelimitedArrayModelBinder : IModelBinder
public Task BindModelAsync(ModelBindingContext bindingContext)
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
var modelName = bindingContext.ModelName;
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
var values = valueProviderResult
.ToString()
.Split(new char[] ',' , StringSplitOptions.RemoveEmptyEntries);
var elementType = bindingContext.ModelType.GetTypeInfo().GenericTypeArguments[0];
if (values.Length == 0)
bindingContext.Result = ModelBindingResult.Success(Array.CreateInstance(elementType, 0));
else
var converter = TypeDescriptor.GetConverter(elementType);
var typedArray = Array.CreateInstance(elementType, values.Length);
try
for (int i = 0; i < values.Length; ++i)
var value = values[i];
var convertedValue = converter.ConvertFromString(value);
typedArray.SetValue(convertedValue, i);
catch (Exception exception)
bindingContext.ModelState.TryAddModelError(
modelName,
exception,
bindingContext.ModelMetadata);
bindingContext.Result = ModelBindingResult.Success(typedArray);
return Task.CompletedTask;
【讨论】:
我继续更新了我的原始答案,只是为了在 1.1 中起作用。真的很喜欢您为提供商创建的解决方案!bindingContext.ModelType.GetTypeInfo().GenericTypeArguments[0];
在绑定 string[]
时抛出异常,使用 this 而不是 var elementType = bindingContext.ModelType.GetElementType();
不幸的是,这段代码不允许使用单个元素,只假设始终存在分隔符。【参考方案3】:
.NET Core 3 中有一些变化。
Microsoft 已将功能从 AddMvc 方法 (source) 中分离出来。
由于 AddMvc 还包括对 View Controllers、Razor Views 等的支持。如果您不需要在项目中使用它们(例如在 API 中),您可以考虑使用用于 Web API 的 services.AddControllers()控制器。
因此,更新后的代码将如下所示:
public void ConfigureServices(IServiceCollection services)
services.AddControllers()
.AddMvcOptions(opt =>
var mbp = opt.ModelBinderProviders.OfType<ArrayModelBinderProvider>().First();
opt.ModelBinderProviders.Insert(opt.ModelBinderProviders.IndexOf(mbp), new DelimitedArrayModelBinderProvider());
);
【讨论】:
以上是关于ASP.NET Core 1 Web API 模型绑定数组的主要内容,如果未能解决你的问题,请参考以下文章
ASP.NET Core Web API 在 API GET 请求中检索空白记录
带有 EF Core 更新实体的 ASP.Net 核心 Web Api 如何
如何将 ASP.NET Core 5 Web API 控制器操作的失败模型验证结果包装到另一个类中并将响应返回为 OK