Emi*_*ioV 5 c# model asp.net-core
我们的请求模型随着 API 日益复杂的增长而不断增长,我们决定使用复杂类型而不是使用简单类型作为操作参数。
一种典型的类型是IEnumerable
逗号分隔值,例如我们使用https://www.strathweb.com/2017/07/customizing-query-string-parameter-bindingitems=1,2,3,5...
中提供的解决方法解决了从字符串转换为 IEnumerable 的问题-in-asp-net-core-mvc/其中关键点是实现IActionModelConvention
接口来识别标有特定属性的参数[CommaSeparated]
。
一切工作正常,直到我们将简单参数移动到单个复杂参数中,现在我们无法检查实现中的复杂参数IActionModelConvention
。使用时也会发生同样的情况IParameterModelConvention
。请参阅下面的代码:
这工作正常:
public async Task<IActionResult> GetByIds(
[FromRoute]int day,
[BindRequired][FromQuery][CommaSeparated]IEnumerable<int> ids,
[FromQuery]string order)
{
// do something
}
Run Code Online (Sandbox Code Playgroud)
虽然这个变体不起作用
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
}
Run Code Online (Sandbox Code Playgroud)
实现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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,代码正在检查标有CommaSeparatedAttribute
... 的参数,但它不适用于像我的第二个变体中使用的复杂参数。
注意:我对上面提到的帖子中提供的原始代码添加了一些细微的更改,例如CommaSeparatedAttribute
不仅可以用于参数,还可以用于属性,但它仍然不起作用
根据 itminus 的回答,我可以制定出最终的解决方案。正如 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);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
评估属性方法:
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);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
我还更改了逗号分隔属性的定义
[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; }
}
Run Code Online (Sandbox Code Playgroud)
我也改变了其他一些活动部件......但这基本上是最重要的。现在,我们可以使用这样的模型:
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
}
Run Code Online (Sandbox Code Playgroud)
对于这样的请求(与上面定义的不完全相同,但非常相似):
如果有人需要完整的代码,请告诉我。再次感谢 itminus 的宝贵帮助