【问题标题】:ASP .Net Core empty array query argument binding exceptionASP .Net Core 空数组查询参数绑定异常
【发布时间】:2019-09-16 01:01:51
【问题描述】:

我有一个如下所示的 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 个元素。所有空值。不完全是我所期望的。 很好的解决方法,但根本不是解决方案。

【问题讨论】:

  • 你可以不指定查询字符串参数吗:即:命中/api/values
  • 那将是最简单的解决方案,但不幸的是没有。我必须确保 API 在这种情况下有效。
  • 那么问题出在哪里?如果您使参数可以为空,则一切正常,正如您提到的那样,这是正确的行为。我就是不明白这是什么问题?
  • 对有关您的评论的问题添加了一些解释。

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


【解决方案1】:

我已经使用自定义 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)。而且浏览器地址栏中的查询字符串也不好看。

【讨论】:

    【解决方案2】:

    您可以创建自定义模型绑定器,其任务是删除默认生成的验证错误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());
        })
    

    【讨论】:

    • 当用户提供一个值时会发生什么,集合仍然会被填充
    • 这个问题是关于任何类型的集合,而不仅仅是枚举。即使对于整数也会发生相同的验证错误。另外,我不喜欢删除验证状态的想法,即使只是针对一种特定类型。
    • 这不应该是引用类型的问题
    【解决方案3】:

    您有几个选择。您可以创建一个自定义模型绑定器来处理您的过滤器类型或者:

    您可以使用 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>());
    

    【讨论】:

    • 谢谢,但这似乎是一种解决方法,同时使用一种解决方法。
    • 是的,那么您可以创建自定义模型绑定器
    猜你喜欢
    • 2017-04-22
    • 2020-11-05
    • 2018-05-08
    • 2018-11-02
    • 1970-01-01
    • 2017-06-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多