我们的请求模型随着API复杂性的增长而增长,因此我们决定使用复杂类型,而不是对操作参数使用简单类型。
一种典型的类型是IEnumerable
,用于逗号分隔的值,例如items=1,2,3,5...
,我们解决了使用https://www.strathweb.com/2017/07/customizing-query-string-parameter-binding-in-asp-net-core-mvc/中提供的解决方法将字符串转换为IEnumerable的问题,其中关键点是实现IActionModelConvention
接口以识别标有特定属性[CommaSeparated]
的参数。
在将简单参数移动到单个复杂参数之前,一切工作都很好,现在我们无法在IActionModelConvention
实现中检查复杂参数。使用IParameterModelConvention
也会发生同样的情况。请查看下面的代码:
这很好:
public async Task<IActionResult> GetByIds(
[FromRoute]int day,
[BindRequired][FromQuery][CommaSeparated]IEnumerable<int> ids,
[FromQuery]string order)
{
// do something
}
此变体不起作用
public class GetByIdsRequest
{
[FromRoute(Name = "day")]
public int Day { get; set; }
[BindRequired]
[FromQuery(Name = "ids")]
[CommaSeparated]
public IEnumerable<int> Ids { get; set; }
[FromQuery(Name = "order")]
public string Order { get; set; }
}
public async Task<IActionResult> GetByIds(GetByIdsRequest request)
{
// do something
}
IActionModelConvention
的实现非常简单:
public void Apply(ActionModel action)
{
SeparatedQueryStringAttribute attribute = null;
for (int i = 0; i < action.Parameters.Count; i++)
{
var parameter = action.Parameters[i];
var commaSeparatedAttr = parameter.Attributes.OfType<CommaSeparatedAttribute>().FirstOrDefault();
if (commaSeparatedAttr != null)
{
if (attribute == null)
{
attribute = new SeparatedQueryStringAttribute(",", commaSeparatedAttr.RemoveDuplicatedValues);
parameter.Action.Filters.Add(attribute);
}
attribute.AddKey(parameter.ParameterName);
}
}
}
如您所见,代码正在检查标记为CommaSeparatedAttribute
的参数...但是它不适用于复杂参数,例如第二个变量中使用的参数。
注意:我对上述文章中提供的原始代码进行了一些小的更改,例如使CommaSeparatedAttribute
不仅可以用于参数,还可以用于属性,但仍然无法使用
答案 0 :(得分:0)
那是因为您试图检测存在 [CommaSeparated]
属性的情况,该属性在参数上修饰(而不是参数的属性):>
var commaSeparatedAttr = parameter.Attributes.OfType()。FirstOrDefault();
请注意,您的操作方法如下所示:
公共异步任务GetByIds(GetByIdsRequest请求)
换句话说,parameter.Attributes.OfType<CommaSeparatedAttribute>()
仅会在request
参数上修饰那些注释。但是,根本没有这样的[CommaSeparatedAttribute]
。
结果, SeparatedQueryStringAttribute
过滤器从未添加到parameter.Action.Filters
。
好像您在SeparatedQueryStringAttribute
里度过了小两周。由于我们没有得到您的代码,请假设我们有一个SeparatedQueryStringAttribute
过滤器(从您上面提到的博客复制):
public class SeparatedQueryStringAttribute : Attribute, IResourceFilter
{
private readonly SeparatedQueryStringValueProviderFactory _factory;
public SeparatedQueryStringAttribute() : this(",") { }
public SeparatedQueryStringAttribute(string separator) {
_factory = new SeparatedQueryStringValueProviderFactory(separator);
}
public SeparatedQueryStringAttribute(string key, string separator) {
_factory = new SeparatedQueryStringValueProviderFactory(key, separator);
}
public void OnResourceExecuting(ResourceExecutingContext context) {
context.ValueProviderFactories.Insert(0, _factory);
}
public void OnResourceExecuted(ResourceExecutedContext context) { }
}
实际上,根据您的GetByIdsRequest
类,我们应该检测存在存在于参数属性上的 [CommaSeparated]
属性:
// CommaSeparatedQueryStringConvention::Apply(action)
public void Apply(ActionModel action)
{
for (int i = 0; i < action.Parameters.Count; i++)
{
var parameter = action.Parameters[i];
var props = parameter.ParameterType.GetProperties()
.Where(pi => pi.GetCustomAttributes<CommaSeparatedAttribute>().Count() > 0)
;
if (props.Count() > 0)
{
var attribute = new SeparatedQueryStringAttribute(",");
parameter.Action.Filters.Add(attribute);
break;
}
}
}
现在对我来说很好。
答案 1 :(得分:0)
基于itminus的答案,我可以制定出最终的解决方案。正如我所指出的那样,这个技巧是在IActionModelConvention实现中。请参阅我的实现,其中考虑了其他方面,例如嵌套模型以及分配给每个属性的真实名称:
public void Apply(ActionModel action)
{
SeparatedQueryStringAttribute attribute = null;
for (int i = 0; i < action.Parameters.Count; i++)
{
var parameter = action.Parameters[i];
var commaSeparatedAttr = parameter.Attributes.OfType<CommaSeparatedAttribute>().FirstOrDefault();
if (commaSeparatedAttr != null)
{
if (attribute == null)
{
attribute = new SeparatedQueryStringAttribute(",", commaSeparatedAttr.RemoveDuplicatedValues);
parameter.Action.Filters.Add(attribute);
}
attribute.AddKey(parameter.ParameterName);
}
else
{
// here the trick to evaluate nested models
var props = parameter.ParameterInfo.ParameterType.GetProperties();
if (props.Length > 0)
{
// start the recursive call
EvaluateProperties(parameter, attribute, props);
}
}
}
}
EvaluateProperties方法:
private void EvaluateProperties(ParameterModel parameter, SeparatedQueryStringAttribute attribute, PropertyInfo[] properties)
{
for (int i = 0; i < properties.Length; i++)
{
var prop = properties[i];
var commaSeparatedAttr = prop.GetCustomAttributes(true).OfType<CommaSeparatedAttribute>().FirstOrDefault();
if (commaSeparatedAttr != null)
{
if (attribute == null)
{
attribute = new SeparatedQueryStringAttribute(",", commaSeparatedAttr.RemoveDuplicatedValues);
parameter.Action.Filters.Add(attribute);
}
// get the binding attribute that implements the model name provider
var nameProvider = prop.GetCustomAttributes(true).OfType<IModelNameProvider>().FirstOrDefault(a => !IsNullOrWhiteSpace(a.Name));
attribute.AddKey(nameProvider?.Name ?? prop.Name);
}
else
{
// nested properties
var props = prop.PropertyType.GetProperties();
if (props.Length > 0)
{
EvaluateProperties(parameter, attribute, props);
}
}
}
}
我还更改了逗号分隔属性的定义
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)]
public class CommaSeparatedAttribute : Attribute
{
public CommaSeparatedAttribute()
: this(true)
{ }
/// <summary>
/// ctor
/// </summary>
/// <param name="removeDuplicatedValues">remove duplicated values</param>
public CommaSeparatedAttribute(bool removeDuplicatedValues)
{
RemoveDuplicatedValues = removeDuplicatedValues;
}
/// <summary>
/// remove duplicated values???
/// </summary>
public bool RemoveDuplicatedValues { get; set; }
}
我也更改了其他活动部件...但这基本上是最重要的部件。现在,我们可以使用这样的模型:
public class GetByIdsRequest
{
[FromRoute(Name = "day")]
public int Day { get; set; }
[BindRequired]
[FromQuery(Name = "ids")]
[CommaSeparated]
public IEnumerable<int> Ids { get; set; }
[FromQuery(Name = "include")]
[CommaSeparated]
public IEnumerable<IncludingOption> Include { get; set; }
[FromQuery(Name = "order")]
public string Order { get; set; }
[BindProperty(Name = "")]
public NestedModel NestedModel { get; set; }
}
public class NestedModel
{
[FromQuery(Name = "extra-include")]
[CommaSeparated]
public IEnumerable<IncludingOption> ExtraInclude { get; set; }
[FromQuery(Name = "extra-ids")]
[CommaSeparated]
public IEnumerable<long> ExtraIds { get; set; }
}
// the controller's action
public async Task<IActionResult> GetByIds(GetByIdsRequest request)
{
// do something
}
对于这样的请求(与上面定义的请求不完全相同,但非常相似):
如果有人需要完整的代码,请告诉我。再次感谢itminus的宝贵帮助