GetPathByAction creates wrong query params for Dictionary<string,string>

Viewed 173

I'm using the GetPathByAction method of the ASP.NET 5 LinkGenerator class to create links for my controller actions. This works well in general, but I just ran into a case where it creates bad query parameters. In this case I pass in the query parameters and use the current controller and action:

 GetPathByAction(httpContextAccessor.HttpContext, null, null, values);

values here is an object that has a few members, the problematic one being the MyDict member which is a Dictionary<string,string> with one key "foo" with the value "bar".

The query string when I call this endpoint is something like the following:

 /myEndpoint?page=1&myDict[foo]=bar

This is properly handled by the model binding and I get my object with the myDict dictionary and the proper values. When I now try to modify the page parameter and generate a new URL with GetPathByAction I get the following output from it:

/myEndpoint?page=2&myDict=[foo, bar]

The output is URL-encoded, I decoded it here to make this easier to read. To me this output seems to be simply wrong, it treats the dictionary as if it were an array (and even then this is not the way arrays are treated in model binding).

My expectation here was that the ASP.NET Url-generating functions would work the same way as the model binding. If ASP.NET can understand the parameter from a URL, it should be able to generate a valid URL from that parameter again.

Am I doing something wrong here or is this a bug? Are there any other ways to generate URLs for a controller action with specific parameters that can handle Dictionary parameters?

2 Answers

I think the easiest solution to capture all parameters into a dictionary (which is supported out-of-the-box) is the following.

Let's suppose your GET method signature looks like this:

public IActionResult Get(int page, [FromQuery] Dictionary<string,string> mydict)

then if you call this endpoint like this:

/yourController?page=1&prop1=string1&prop2=string2&prop3=string3
  • your page parameter will contain 1
  • your mydict parameter will have 4 key-value pairs
    • "page":"1"
    • "prop1":"string1"
    • "prop2":"string2"
    • "prop3":"string3"

You can simple get rid of the extra key by calling the following:

if(mydict.ContainsKey(nameof(page))) mydict.Remove(nameof(page));

You can pass the dictionary to the GetPathByAction as an already formatted queryString:

var mydictAsQueryString = string.Join("&", mydict.Select(_ => $"{_.Key}={_.Value}"));

There are a couple of built-in helper classes (like QueryHelpers, QueryBuilder, HttpUtility, etc.) but I've found this snippet is quite simple and easy to use anywhere.

In this case MVC binding for collections does indeed seem slightly weird.

The bind provider for dictionary types, extends the behaviour of binding a simple collection like List<KeyPair<K,V>>. With each element bound by a special KeyPair<K,V> binder. Before converting that collection to a dictionary via Linq.

Then the dictionary binder will look for additional prefix[Key]=Value or prefix.Key=Value values.

Related