【发布时间】:2021-11-17 10:36:23
【问题描述】:
我正在尝试实现自定义绑定器以允许在查询字符串中使用逗号分隔列表。基于this blog post 和official documentation 我已经创建了一些解决方案。但是,我不想使用属性来装饰想要的属性,我想让这种行为默认为所有简单类型的集合(IList<T>, List<T>, T[], IEnumerable<T>... 其中T 是int, string, short...)
但由于手动创建ArrayModelBinderProvider、CollectionModelBinderProvider 并将bindingContext.ValueProvider 替换为CommaSeparatedQueryStringValueProvider,因此该解决方案看起来非常hacky,我相信应该有更好的方法来实现相同的目标。
public class CommaSeparatedQueryBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var bindingSource = context.BindingInfo.BindingSource;
if (bindingSource != null && bindingSource != BindingSource.Query)
{
return null;
}
if (!context.Metadata.IsEnumerableType)
{
return null;
}
if (context.Metadata.ElementMetadata.IsComplexType)
{
return null;
}
IModelBinderProvider modelBinderProvider;
if (context.Metadata.ModelType.IsArray)
{
modelBinderProvider = new ArrayModelBinderProvider();
}
else
{
modelBinderProvider = new CollectionModelBinderProvider();
}
var binder = modelBinderProvider.GetBinder(context);
return new CommaSeparatedQueryBinder(binder);
}
}
public class CommaSeparatedQueryBinder : IModelBinder
{
private readonly IModelBinder _modelBinder;
public CommaSeparatedQueryBinder(IModelBinder modelBinder)
{
_modelBinder = modelBinder;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var valueProviderLazy = new Lazy<CommaSeparatedQueryStringValueProvider>(() =>
new CommaSeparatedQueryStringValueProvider(bindingContext.HttpContext.Request.Query));
if (bindingContext.ValueProvider is CompositeValueProvider composite
&& composite.Any(provider => provider is QueryStringValueProvider))
{
var queryStringValueProvider = composite.First(provider => provider is QueryStringValueProvider);
var index = composite.IndexOf(queryStringValueProvider);
composite.RemoveAt(index);
composite.Insert(index, valueProviderLazy.Value);
await _modelBinder.BindModelAsync(bindingContext);
composite.RemoveAt(index);
composite.Insert(index, queryStringValueProvider);
}
else if(bindingContext.ValueProvider is QueryStringValueProvider)
{
var originalValueProvider = bindingContext.ValueProvider;
bindingContext.ValueProvider = valueProviderLazy.Value;
await _modelBinder.BindModelAsync(bindingContext);
bindingContext.ValueProvider = originalValueProvider;
}
else
{
await _modelBinder.BindModelAsync(bindingContext);
}
}
}
public class CommaSeparatedQueryStringValueProvider : QueryStringValueProvider
{
private const string Separator = ",";
public CommaSeparatedQueryStringValueProvider(IQueryCollection values)
: base(BindingSource.Query, values, CultureInfo.InvariantCulture)
{
}
public override ValueProviderResult GetValue(string key)
{
var result = base.GetValue(key);
if (result == ValueProviderResult.None)
{
return result;
}
if (result.Values.Any(x => x.IndexOf(Separator, StringComparison.OrdinalIgnoreCase) > 0))
{
var splitValues = new StringValues(result.Values
.SelectMany(x => x.Split(Separator))
.ToArray());
return new ValueProviderResult(splitValues, result.Culture);
}
return result;
}
}
Startup.cs
services.AddControllers(options =>
{
options.ModelBinderProviders.Insert(0, new CommaSeparatedQueryBinderProvider());
})
【问题讨论】:
-
为什么不对列表和数组使用普通的活页夹,而不是创建自定义活页夹?
-
我正在将 NancyFx 迁移到 Asp.Net Core。 NancyFx 支持将查询字符串中的列表作为逗号分隔值传递:
?list=val1,val2,val3,而 Asp.Net Core 仅支持这种表示形式:?list=val1&list=val2&list=val3。所以为了保持系统向后兼容,我需要支持旧样式。
标签: c# asp.net-core custom-model-binder