ASP .Net Core空数组查询参数绑定异常

时间:2019-04-26 13:38:30

标签: c# asp.net-core json.net asp.net-core-2.2

我有一个如下所示的WebAPI控制器:

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    [HttpGet]
    public ActionResult<string> Get([FromQuery]DataFilter dataFilter)
    {
        return string.Join(Environment.NewLine, dataFilter?.Filter?.Select(f => f.ToString()) ?? Enumerable.Empty<string>());
    }
}

没什么,只是一个控制器,它从查询字符串中删除一些数据并将其作为响应输出。 它收到的类如下:

public class DataFilter
{
    public IEnumerable<FilterType> Filter { get; set; }
}

public enum FilterType
{
    One,
    Two,
    Three,
}

这些只是用于说明问题的示例类,当尝试这样调用此方法时,这是一个验证错误:

/api/values?filter=

响应:

{
  "errors": {
    "Filter": [
      "The value '' is invalid."
    ]
  },
  "title": "One or more validation errors occurred.",
  "status": 400,
  "traceId": "80000164-0002-fa00-b63f-84710c7967bb"
}

如果我将FilterType设置为可为空,则可以使用,但是在这种情况下,数组仅包含空值。如果这样使用:

/api/values?filter=&filter=&filter=

它将仅包含3个空值。而且我希望它只是为空或为null,因为没有传递任何实际值。

ASP .Net Core github帐户包含一些类似的问题,但是据报告,它们已在我正在使用的2.2中修复。但是也许它们不同,或者我误会了一些东西。

EDIT_0 : 只是为了说明我对可为空的含义。

如果我将班级更改为此:

public IEnumerable<FilterType?> Filter { get; set; } //notice that nullable is added to an Enum, not the list

然后当这样调用时:

/api/values?filter=&filter=&filter=

我在“过滤器”属性中获得了3个元素。全部为空。不完全是我的期望。 好的解决方法,但根本不是解决方案。

3 个答案:

答案 0 :(得分:0)

您有一些选择。您可以创建自定义模型联编程序来处理您的过滤器类型或:

您可以使用Nullables创建IEnumerable:

public IEnumerable<FilterType?> Filter { get; set; }

并过滤掉调用代码中的空值:

return string.Join(Environment.NewLine, dataFilter?.Filter?.Where(f => f != null)
   .Select(f => f.ToString()) ?? Enumerable.Empty<string>());

答案 1 :(得分:0)

您可以创建自定义模型联编程序,其任务是删除默认CollectionModelBinder生成的验证错误。在您的情况下,这应该足够了,因为默认模型绑定程序可以根据您的需要工作,不会向集合添加无效值。

public class EmptyCollectionModelBinder : CollectionModelBinder<FilterType>
{
    public EmptyCollectionModelBinder(IModelBinder elementBinder) : base(elementBinder)
    {
    }

    public override async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        await base.BindModelAsync(bindingContext);
        //removing validation only for this collection
        bindingContext.ModelState.ClearValidationState(bindingContext.ModelName);
    }
}

创建并注册模型绑定程序提供程序

public class EmptyCollectionModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (context.Metadata.ModelType == typeof(IEnumerable<FilterType>))
        {
            var elementBinder = context.CreateBinder(context.MetadataProvider.GetMetadataForType(typeof(FilterType)));

            return new EmptyCollectionModelBinder(elementBinder);
        }

        return null;
    }
}

Startup.cs

services
    .AddMvc(options =>
    {
        options.ModelBinderProviders.Insert(0, new EmptyCollectionModelBinderProvider());
    })

答案 2 :(得分:0)

我已经使用自定义TypeConverter解决了这种情况,并将其移至JSON格式以传递数组(例如filter = [“ one”,“ two”])

这是我的定义方式:

public class JsonArrayTypeConverter<T> : TypeConverter
{
    private static readonly TypeConverter _Converter = TypeDescriptor.GetConverter(typeof(T));

    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) =>
        sourceType == typeof(string) || TypeDescriptor.GetConverter(sourceType).CanConvertFrom(context, sourceType);

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        try
        {
            return JsonConvert.DeserializeObject<IEnumerable<T>>((string)value);
        }
        catch (Exception)
        {
            var dst = _Converter.ConvertFrom(context, culture, value); //in case this is not an array or something is broken, pass this element to a another converter and still return it as a list
            return new T[] { (T)dst };
        }
    }
}

以及全球注册:

TypeDescriptor.AddAttributes(typeof(IEnumerable<FilterType>), new TypeConverterAttribute(typeof(JsonArrayTypeConverter<FilterType>)));

现在,我的过滤器列表中没有空项目,并且还支持具有多种类型(枚举,字符串,整数等)的JSON列表。

唯一的缺点是,这将无法像以前那样与传递元素一起使用(例如filter = one&filter = two&filter = three)。而且浏览器地址栏中的查询字符串也不美观。