简体   繁体   English

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

[英]Dot net core route list of parameters binding

I am trying to parse the list of parameters (long in my case) from the route in dot net core.我正在尝试从 dot net core 中的路由解析参数列表(在我的情况下很长)。 Therefore I want something like this因此我想要这样的东西

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

I know it doesn't work by default, I also know it works via the query string.我知道默认情况下它不起作用,我也知道它通过查询字符串起作用。 However, for consistency, I would like to keep it as route parameter.但是,为了一致性,我想将其保留为路由参数。

Is it possible to extend FromRoute in dot net core and implement this behavior?是否可以在 dot net core 中扩展 FromRoute 并实现此行为?

So far I managed to create an action filter which technically works, but it requires the extra attribute, default FromRoute is still creating errors in the model state (especially the last part is obviously not acceptable).到目前为止,我设法创建了一个技术上有效的动作过滤器,但它需要额外的属性,默认 FromRoute 仍然在模型状态中创建错误(尤其是最后一部分显然是不可接受的)。

My current code for attribute parts of which might be reusable for a proper implementation.我当前的属性部分代码可能可以重用以进行正确的实现。

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));
    }
}

You can use it like this你可以像这样使用它

[HttpGet("{ids?}")]
[ArrayInput("ids")]
[Produces(typeof(TestWebResponseDTO))]
public async Task<IActionResult> Get(long[] ids)
{
}

I honestly don't understand how is it possible that more people haven't encountered this problem before.老实说,我不明白怎么可能有更多人以前没有遇到过这个问题。 I though I'll be lazy and look for answers but as usual, it seems it's best idea to take a swing at it myself.虽然我会很懒惰并寻找答案,但像往常一样,自己尝试一下似乎是最好的主意。 Here's a finished, working somewhat of a prototype but so far I am happy with it.这是一个完成的,有点原型的工作,但到目前为止我对它很满意。

Usage:用法:

[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: 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;
    }
}

Constants:常数:

public static class RouteArrayConstants
{
    public const string NUMBER_ARRAY = "regex(^\\d+(,\\d+)*$)";

    public const string STRING_ARRAY = "regex(^\\s+(,\\s+)*$)";
}

Setup:设置:

services.AddMvc(cfg =>
{
    cfg.ModelBinderProviders.Insert(0, new RouteArrayModelBinderProvider());
});

Sidenote: If you are using Swagger to document your api (which you should), not that swagger specification forces route parameters to be required.旁注:如果您使用 Swagger 来记录您的 api(您应该这样做),那么 swagger 规范不会强制要求路由参数。 That's why you will need an extra action with no ids if you want to fetch all resources regardless of their ids.这就是为什么如果您想获取所有资源而不管它们的 ID,您将需要一个没有 ID 的额外操作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM