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 中使用 FromQueryFromRoute notHttpGet 启用逗号分隔的隐式数组模型绑定(它在 ASP.NET Web API 和 ASP.NET MVC 中实现) . @bzlm 在问题中没有提到绑定逗号分隔的数组值。你从哪里得到的? “数组”一词在问题标题中(“模型绑定数组”),代码示例将IEnumerable&lt;int&gt; ints作为操作的参数。 :) 为清楚起见,编辑了问题。你知道答案吗(是否使用FromQuery)? 没有提到“逗号分隔”这个短语,这是我要求澄清的地方。像/api/values?ints=1&amp;ints=2&amp;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

ASP.NET Core Web API - 当前上下文中不存在名称“jsonString”

Asp.Net Core 1.1 消费web api

将文件从 ASP.NET Core Web api 发布到另一个 ASP.NET Core Web api