1

I want to send a HTTP request that looks like this: http://api.com/main?id=1234&id=5678, the id will be GUID in string eventually.

I tried the below piece of code:

var idString = string.Join(",", listOfIds);

var queryString = new Dictionary<string, string>
{
    {"id", idString}
};

requestUri = QueryHelpers.AddQueryString(requestUri, queryString);

This will give me like: http://api.com/main?id=1234,5678 but I want the style like above.

Is there anyway to achieve this without using for loop?

Thanks!

Sivvie Lim
  • 784
  • 2
  • 14
  • 43
  • 1
    Why not use a loop? `String.Join` itself uses a loop and a StringBuilder. You can also use `&id=` as the separator and prepend that with `id=` – Panagiotis Kanavos Jan 20 '21 at 07:54
  • Hmm ok let me try it~ – Sivvie Lim Jan 20 '21 at 08:07
  • Seriously, what's the question? Why not use a loop? Are you trying to avoid temporary strings? Don't want to write a function tot do the job? – Panagiotis Kanavos Jan 20 '21 at 08:09
  • The first id will have `?id=` instead of `&id=` so I'm not sure how can I use loops to achieve that.. – Sivvie Lim Jan 20 '21 at 08:11
  • Add it to the StringBuilder before starting the loop. `?` will be added by `AddQueryString` itself – Panagiotis Kanavos Jan 20 '21 at 08:12
  • I changed the separator to `&id=` but when it adds as query string it does not recognize it? The whole URI turns out like this `http://api.com/main?id=1234%26id%5678`. – Sivvie Lim Jan 20 '21 at 08:39
  • You've already built the query string. You don't need `AddQueryString` any more – Panagiotis Kanavos Jan 20 '21 at 08:45
  • Which .NET Core version do you use? .NET Core 5 added the ability to work with arrays of values using [AddQueryString(String, IEnumerable>)](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.webutilities.queryhelpers.addquerystring?view=aspnetcore-5.0#Microsoft_AspNetCore_WebUtilities_QueryHelpers_AddQueryString_System_String_System_Collections_Generic_IEnumerable_System_Collections_Generic_KeyValuePair_System_String_System_String___) and `AddQueryString(String, IEnumerable>)` – Panagiotis Kanavos Jan 20 '21 at 08:54

2 Answers2

4

QueryHelpers doesn't work with arrays because there's no standard way to pass an array of values in a query string. Some applications accept id=1,2,3 others id=1&id=2&id=3 while others id[0]=1&id[1]=2&id[2]=3.

.NET (Core) 5 and later

AddQueryString now works with lists of KeyValuePair<string,string>or KeyValuePair<string,StringValues>

var parameters=new []{
                   new KeyValuePair<string,string>("id",new StringValues(arrayOfIds)),
                   new KeyValuePair<string,string>("other","value"),
                   ...
               };
var finalUri=QueryHelpers.AddQueryString(requestUri, parameters);

The StringValues constructors accept either a single string or an array of strings

Before .NET (Core) 5

String.Join itself uses a loop and a StringBuilder to create a new string without allocating temporary strings. Strings are immutable, so any string modification operation results in a new temporary string.

You could use the source code as a guide to build your own loop. A quick solution could be something like this:

string ArrayToQueryString_DONT_USE(string name,string[] values)
{
    var result=new StringBuilder();
    result.AppendFormat("{0}={1}",name,value);
    for(int i=1;i<values.Length;i++)
    {
        result.AppendFormat("&{0}={1}',name,values[i]);
    }
    return result.ToString();
}

Unfortunately, that won't work if the parameter names or values need encoding. That's what AddQueryString does, using, once again, a StringBuilder to avoid allocating temporary strings. We can borrow that code as well:

string ArrayToQueryString(string name,string[] values)
{
    var result=new StringBuilder();
    result.AppendFormat("{0}={1}",name,value);
    for(int i=1;i<values.Length;i++)
    {
        result.Append('&');
        result.Append(UrlEncoder.Default.Encode(name));
        result.Append('=');
        result.Append(UrlEncoder.Default.Encode(values[i]));
    }
    return result.ToString();
}
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • I got it! Thanks a lot!! – Sivvie Lim Jan 20 '21 at 09:52
  • 1
    It gives me error Argument 2: cannot convert from 'System.Collections.Generic.KeyValuePair[]' to 'System.Collections.Generic.IDictionary' – Syed Mohammad Fahim Abrar Aug 26 '21 at 05:43
  • Heads up: Your syntax ...new KeyValuePair("other","value")... produces an error: "cannot create an instance of the static class...". See: https://stackoverflow.com/questions/15495165/how-to-initialize-keyvaluepair-object-the-proper-way – Design.Garden Apr 23 '23 at 12:26
0

This will give me like: http://api.com/main?id=1234,5678 but I want the style like above, e.g., http://api.com/main?id=1234&id=5678

I think the accept answer didn't address this issue from OP.

Using AddQueryString(string uri, IEnumerable<KeyValuePair<string, string>> queryString) overload when you have an array you want to be part of the query string would only give you the comma-separated string as the parameter, and most of the time that is not what you want.

For example,

var endpoint = "v1/api/endpoint";
var arrayOfIds = new[] { "1", "2", "3" };
var queryString = new[]
{
    new KeyValuePair<string,string>("id",new StringValues(arrayOfIds)),
    new KeyValuePair<string,string>("other","value"),
    ...
};

var url = QueryHelpers.AddQueryString(endpoint, queryString);

The url at the end would look like (demo)

v1/api/endpoint?id=1,2,3&other=value

To get what OP wants, we will need to use the StringValues' overload AddQueryString(string uri, IEnumerable<KeyVaulePair<string, StringValues>> queryString).

For example,

var endpoint = "v1/api/endpoint";
var arrayOfIds = new[] { "1", "2", "3" };
var queryString = new []
{
    new KeyValuePair<string, StringValues>("id", new StringValues(arrayOfIds)),
    new KeyValuePair<string, StringValues>("other", "value")
};
        
var url = QueryHelpers.AddQueryString(endpoint, queryString);

Then the URL would look like (demo)

v1/api/endpoint?id=1&id=2&id=3&other=value

Bonus

Just in case you wonder why you don't see the StringValues' overload, even you are running on .NET 5+?

Maybe you have multiple versions of .NET installed, and your app is still referencing the old version 2.*, which is marked as deprecated recently.

To make sure you use the latest .NET, in your .csproj file, you can add the framework reference like the following:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>net6.0</TargetFramework>
        ...
    </PropertyGroup>

    <ItemGroup>
        <FrameworkReference Include="Microsoft.AspNetCore.App" />
    </ItemGroup>

    ...
</Project>
David Liang
  • 20,385
  • 6
  • 44
  • 70