点网核心路由参数绑定列表

时间:2017-10-12 22:14:47

标签: c# asp.net-core

我试图从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)
{
}

1 个答案:

答案 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的额外动作。