Исходя из ответа 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
}
Для запроса, подобного этому (не совсем так, как определено выше, но очень похоже):
HTTP: //.../vessels/algo/days/20190101/20190202/hours/1/2 страница = 2 & размер = 12 & фильтр = Eq (а, б) и порядок = (по возрастанию (а) ) и включают в себя = все, ни один & DS = 12,34,45 и экстра-включают = все, ни один и дополнительные идентификаторы = 12,34,45
Если кому-то нужен полный код, пожалуйста, дайте мне знать. Еще раз спасибо itminus за его ценную помощь