【问题标题】:Dot net core route list of parameters bindingdot net core 路由列表参数绑定
【发布时间】:2017-10-13 23:10:13
【问题描述】:

我正在尝试从 dot net core 中的路由解析参数列表(在我的情况下为长)。所以我想要这样的东西

[HttpGet("{ids?}")]
public async Task<IActionResult> Get([FromRoute, Optional]long[] ids)
{
}

我知道默认情况下它不起作用,我也知道它通过查询字符串起作用。但是,为了保持一致性,我想将其保留为路由参数。

是否可以在 dot net core 中扩展 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)
{
}

【问题讨论】:

  • 它需要很长[] 还是自定义 DTO 可以工作?因为那时 CustomModelBinders 可能会为您执行此操作。
  • 如果它的 ID 集合为什么你不使用HttpPost.? URL 有字符限制
  • @MindingData 它应该是 long/int ,因为这是最有意义的。尽管自定义模型绑定器很有趣,但它不适用于数组或列表吗?
  • @LP13 绝对不能发帖。它得到了,我没有在服务器上创建任何数据,我只是根据 id 获取一些数据。
  • @MindingData 我找到了这个stevejgordon.co.uk/html-encode-string-aspnet-core-model-binding,这家伙实现了字符串的自定义绑定。也许我可以用 lists/arrays 做类似的事情。我明天去看看

标签: c# asp.net-core


【解决方案1】:

老实说,我不明白为什么更多的人以前没有遇到过这个问题。我虽然会很懒惰并寻找答案,但像往常一样,最好自己动手尝试一下。这是一个完成的,有点像原型的工作,但到目前为止我对它很满意。

用法:

[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 的额外操作。

【讨论】:

    猜你喜欢
    • 2011-10-04
    • 1970-01-01
    • 2017-04-22
    • 2020-11-05
    • 1970-01-01
    • 2020-04-03
    • 2021-12-29
    • 1970-01-01
    • 2017-12-27
    相关资源
    最近更新 更多