9

How do you model bind an array from the URI with GET in ASP.NET Core 1 Web API (implicitly or explicitly)?

In ASP.NET Web API pre Core 1, this worked:

[HttpGet]
public void Method([FromUri] IEnumerable<int> ints) { ... }

How do you do this in ASP.NET Web API Core 1 (aka ASP.NET 5 aka ASP.NET vNext)? The docs have nothing.

4

3 回答 3

17

该类FromUriAttribute结合了FromRouteAttributeFromQueryAttribute类。根据您的路由配置/发送的请求,您应该能够将您的属性替换为其中之一。

但是,有一个可用的垫片可以为您提供FromUriAttribute课程。通过包资源管理器安装“Microsoft.AspNet.Mvc.WebApiCompatShim”NuGet 包,或直接将其添加到您的 project.json 文件中:

"dependencies": {
  "Microsoft.AspNet.Mvc.WebApiCompatShim": "6.0.0-rc1-final"
}

虽然它有点旧,但我发现这篇文章很好地解释了一些变化。

捆绑

如果您要为数组绑定逗号分隔值 ("/api/values?ints=1,2,3"),您将需要像以前一样的自定义活页夹。这是用于 ASP.NET Core的Mrchief 解决方案的改编版本。

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)
于 2016-04-29T14:41:38.070 回答
7

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;
    }
}
于 2017-04-27T11:06:03.323 回答
0

.NET Core 3 中有一些变化。

Microsoft 已将功能从 AddMvc 方法 ( s​​ource ) 中分离出来

由于 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());
            });
}
于 2019-12-11T11:22:52.570 回答