ASP.Net Core空数组查询参数绑定异常
ASP .Net Core empty array query argument binding exception
我有一个如下所示的 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>());
}
}
没什么特别的,只是一个控制器,它从查询字符串中接收一些数据并将其作为响应输出。
它收到的 class 看起来像这样:
public class DataFilter
{
public IEnumerable<FilterType> Filter { get; set; }
}
public enum FilterType
{
One,
Two,
Three,
}
这些只是示例 classes 来说明问题,当尝试像这样调用此方法时出现验证错误:
/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 设置为可为 null,它会起作用,但在这种情况下数组只包含 null 值。如果这样使用:
/api/values?filter=&filter=&filter=
它将只包含 3 个空值。我希望它只是空的或 null,因为没有传递任何实际值。
ASP.Net Core github 帐户包含一些类似的问题,但据报道它们已在我正在使用的 2.2 中修复。但也许他们是不同的或者我误解了什么。
EDIT_0:
只是为了说明我对可空性的意思。
如果我将 class 更改为:
public IEnumerable<FilterType?> Filter { get; set; } //notice that nullable is added to an Enum, not the list
然后这样调用时:
/api/values?filter=&filter=&filter=
我在 "Filter" 属性 中得到了 3 个元素。所有空值。不完全是我所期望的。
作为解决方法很好,但根本不是解决方案。
你有几个选择。您可以创建一个自定义模型活页夹来处理您的过滤器类型或者:
您可以使用可空值创建 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>());
您可以创建自定义模型绑定器,其任务是删除默认生成的验证错误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());
})
我已经使用自定义 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)。浏览器地址栏中的查询字符串也不好看。
我有一个如下所示的 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>());
}
}
没什么特别的,只是一个控制器,它从查询字符串中接收一些数据并将其作为响应输出。 它收到的 class 看起来像这样:
public class DataFilter
{
public IEnumerable<FilterType> Filter { get; set; }
}
public enum FilterType
{
One,
Two,
Three,
}
这些只是示例 classes 来说明问题,当尝试像这样调用此方法时出现验证错误:
/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 设置为可为 null,它会起作用,但在这种情况下数组只包含 null 值。如果这样使用:
/api/values?filter=&filter=&filter=
它将只包含 3 个空值。我希望它只是空的或 null,因为没有传递任何实际值。
ASP.Net Core github 帐户包含一些类似的问题,但据报道它们已在我正在使用的 2.2 中修复。但也许他们是不同的或者我误解了什么。
EDIT_0: 只是为了说明我对可空性的意思。
如果我将 class 更改为:
public IEnumerable<FilterType?> Filter { get; set; } //notice that nullable is added to an Enum, not the list
然后这样调用时:
/api/values?filter=&filter=&filter=
我在 "Filter" 属性 中得到了 3 个元素。所有空值。不完全是我所期望的。 作为解决方法很好,但根本不是解决方案。
你有几个选择。您可以创建一个自定义模型活页夹来处理您的过滤器类型或者:
您可以使用可空值创建 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>());
您可以创建自定义模型绑定器,其任务是删除默认生成的验证错误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());
})
我已经使用自定义 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)。浏览器地址栏中的查询字符串也不好看。