0

I have quite a few places where multidimensional arrays of varying lengths are used as query parameters.

With controllers a request with to /multi?vals[0][0]=5&vals[0][1]=6&vals[1][0]=6&vals[1][1]=7 binds without issues.

    [Route("/multi")]
    public class MultiController : Controller
    {
        [HttpGet("")]
        public IActionResult Index([FromQuery] int[][] vals)
        {
            return this.Ok(string.Join(" ", vals.Select(x => string.Join(":", x))));
        }
    }

With Minimal APIs,

 public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
            builder.Services.AddControllers();
            var app = builder.Build();

            app.MapGet("/multi-minimal", ([FromQuery] int[][] vals) => $"Hello World! {string.Join(":::", vals.Select(x => string.Join(":", x)))}");
            app.MapControllers();
            app.Run();
        }
    }

There is an error trying to map the route

InvalidOperationException: No public static bool int[].TryParse(string, out int[]) method found for vals.
Microsoft.AspNetCore.Http.RequestDelegateFactory.BindParameterFromValue(ParameterInfo parameter, Expression valueExpression, RequestDelegateFactoryContext factoryContext, string source)

I don't see how its possible to register a static TryParse method on an int[].

I have seen some documentation around BindAsync for scenarios like the following:

    public class Dto
    {
        [BindProperty(Name = "coordinates")]
        public int[][] Coordinates { get; set; }

        [BindProperty(Name = "a")]
        public string? PropA { get; set; }

        [BindProperty(Name = "b")]
        public string PropB { get; set; }

      // lots more properties

        public static ValueTask<Dto?> BindAsync(HttpContext context)
        {
            throw new NotImplementedException();
        }
    }

But for larger DTOs it's a tad inconvenient to have to parse the entire query string and set properties that are already supported. This seemed more targeted at 'complex types' but in my case its more 'complex primitives' I am trying to bind.

Any suggestions on how to get app.MapGet("/multi-minimal", ([FromQuery] int[][] vals) => {}"); working with varying size multidimensional arrays would be greatly appreciated.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
bep
  • 952
  • 2
  • 12
  • 21

1 Answers1

1

First of all int[][] is not multidimensional array (that would be int[,]) but a jagged one.

I would argue that this is pretty much covered in the docs:

Binding query strings or header values to an array of complex types is supported when the type has TryParse implemented.

And int[][] is not an array of primitive types, int[] is a complex type in this context. Based on discussions here and here it seems to be a conscious descision.

As for workarounds since .NET 7 you can use AsParameters attribute and do something like the following:

app.MapGet("/testarr", ([AsParameters] Dto dto)
    => ... );

public class MyJaggedIntArrWrapper
{
    public int[][] Arr { get; set; }
    public static ValueTask<MyJaggedIntArrWrapper> BindAsync(HttpContext context)
    {
        return ValueTask.FromResult(new MyJaggedIntArrWrapper
        {
            Arr = new int[0][] // actual parsing from context ...
        });
    }
}

public class Dto
{
    public string? A { get; set; }
    public MyJaggedIntArrWrapper SomeNameDoesNotMatter { get; set; }
}

Which will successfully bind a for /testarr?a=5&vals[0][0]=5&vals[0][1]=6&vals[1][0]=6&vals[1] query and will invoke MyJaggedIntArrWrapper.BindAsync thus removing the need to manually bind what can be bound automatically.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • Lets say I have 2 parameters, both jagged arrays, `public MyJaggedIntArrWrapper A { get; set; }` and `public MyJaggedIntArrWrapper B { get; set; }` , inside BindAsync how can I know if I am parsing the keys for `a[0][0]=,a[1][0]`, etc, or keys for `b[0][0]=,b[1][0]` since BindAsync just receives the entire context how can I tell which keys in the query string I am parsing out? – bep May 11 '23 at 13:49
  • @bep do you really have such use case? Or the question is purely rhetorical? P.S. I would argue that in this case the better option would be switching to post and accept parameters as JSON body. – Guru Stron May 11 '23 at 13:52
  • not exactly the same type, optional parameter for coords of [][] and one of [][][] so they would use different wrappers but even with just single use case, of just vals[0][0]=5&vals[0][1]=6&vals[1][0]=6&vals[1], what in BindAsync tells me to parse out the `vals` key from the querystring? or do I have to hard code it and make sure the `[BindProperty(Name = "vals")]` is always set to match what is hard coded in the wrapper to be checked + bound? – bep May 11 '23 at 14:03
  • @bep Then you can create a class which will contain both and will know how to distinguish those, i.e. `SomeDtoJaggedArraysWrapper` (i.e. it will contain all such properties which can't be automatically bound) and use it instead of `MyJaggedIntArrWrapper` in the `Dto`. – Guru Stron May 11 '23 at 14:05