我试图从dot net core中的路由解析参数列表(在我的情况下很长)。因此我想要这样的东西
[HttpGet("{ids?}")]
public async Task<IActionResult> Get([FromRoute, Optional]long[] ids)
{
}
我知道它默认不起作用,我也知道它可以通过查询字符串工作。但是,为了保持一致性,我想将其保留为路由参数。
是否有可能在点网核心中扩展FromRoute并实现此行为?
到目前为止,我设法创建了一个技术上有效的动作过滤器,但它需要额外的属性,默认的FromRoute仍然在模型状态下创建错误(特别是最后一部分显然是不可接受的)。
我当前的属性部分代码可以重用,以便正确实现。
public class ArrayInputAttribute : ActionFilterAttribute
{
private readonly List<string> _ParameterNames;
public string Separator { get; set; }
public ArrayInputAttribute(params string[] parameterName)
{
_ParameterNames = parameterName.ToList();
Separator = ",";
}
public void ProcessArrayInput(ActionExecutingContext actionContext, string parameterName)
{
if (actionContext.ActionArguments.ContainsKey(parameterName))
{
var parameterDescriptor = actionContext.ActionDescriptor.Parameters.FirstOrDefault(p => p.Name == parameterName);
if (parameterDescriptor != null && parameterDescriptor.ParameterType.IsArray)
{
var type = parameterDescriptor.ParameterType.GetElementType();
var parameters = String.Empty;
if (actionContext.RouteData.Values.ContainsKey(parameterName))
{
parameters = (string)actionContext.RouteData.Values[parameterName];
}
else
{
var queryString = actionContext.HttpContext.Request.Query;
if (queryString[parameterName].Count > 0)
{
parameters = queryString[parameterName];
}
}
try
{
var values = parameters.Split(new[] { Separator }, StringSplitOptions.RemoveEmptyEntries)
.Select(TypeDescriptor.GetConverter(type).ConvertFromString).ToArray();
var typedValues = Array.CreateInstance(type, values.Length);
values.CopyTo(typedValues, 0);
actionContext.ActionArguments[parameterName] = typedValues;
}
catch (System.Exception)
{
(actionContext.Controller as Controller).ViewData.ModelState.AddModelError(parameterDescriptor.Name, "");
}
}
}
}
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
_ParameterNames.ForEach(parameterName => ProcessArrayInput(actionContext, parameterName));
}
}
您可以像这样使用
[HttpGet("{ids?}")]
[ArrayInput("ids")]
[Produces(typeof(TestWebResponseDTO))]
public async Task<IActionResult> Get(long[] ids)
{
}
答案 0 :(得分:2)
老实说,我不明白以前有多少人没有遇到过这个问题。我虽然我会懒得寻找答案,但像往常一样,似乎最好自己动手。这是一个完成,工作有点原型,但到目前为止,我很满意。
用法:
[HttpGet("{ids:" + RouteArrayConstants.NUMBER_ARRAY + "}")]
[Produces(typeof(TestWebResponseDTO))]
public async Task<IActionResult> Get([FromRoute, Required]long[] ids)
{
}
ArrayBinder:
public class RouteArrayModelBinder : IModelBinder
{
private char separator;
public RouteArrayModelBinder(char Separator = ',')
{
separator = Separator;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult != ValueProviderResult.None)
{
var valueAsString = valueProviderResult.FirstValue;
try
{
var type = bindingContext.ModelType.GetElementType();
var values = valueAsString.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries)
.Select(TypeDescriptor.GetConverter(type).ConvertFromString).ToArray();
var typedValues = Array.CreateInstance(type, values.Length);
values.CopyTo(typedValues, 0);
bindingContext.Result = ModelBindingResult.Success(typedValues);
}
catch (System.Exception)
{
bindingContext.Result = ModelBindingResult.Failed();
bindingContext.ModelState.AddModelError(bindingContext.ModelName, $@"Failed to convert ""{valueAsString}"" to ""{bindingContext.ModelType.FullName}""");
}
}
}
}
ArrayBinderProvider:
public class RouteArrayModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType.IsArray)
{
return new RouteArrayModelBinder();
}
return null;
}
}
常量:
public static class RouteArrayConstants
{
public const string NUMBER_ARRAY = "regex(^\\d+(,\\d+)*$)";
public const string STRING_ARRAY = "regex(^\\s+(,\\s+)*$)";
}
设定:
services.AddMvc(cfg =>
{
cfg.ModelBinderProviders.Insert(0, new RouteArrayModelBinderProvider());
});
旁注: 如果您使用Swagger来记录您的api(您应该这样做),那么swagger规范不会强制要求路由参数。这就是为什么如果你想要获取所有资源而不管他们的id,你将需要一个没有id的额外动作。