Coder Perfect

Create a System.Net query string. HttpClient receives

Problem

Is it right that there is no api to add parameters to a http get request when using System.Net.HttpClient?

Is there an easy api for constructing the query string that does not require creating a name value collection, url encoding it, and then concatenating it? I was expecting to use RestSharp’s api (i.e. AddParameter(..)).

Asked by NeoDarque

Solution #1

Yes.

Sure:

var query = HttpUtility.ParseQueryString(string.Empty);
query["foo"] = "bar<>&-baz";
query["bar"] = "bazinga";
string queryString = query.ToString();

will provide you with the intended outcome:

foo=bar%3c%3e%26-baz&bar=bazinga

The UriBuilder class might also come in handy:

var builder = new UriBuilder("http://example.com");
builder.Port = -1;
var query = HttpUtility.ParseQueryString(builder.Query);
query["foo"] = "bar<>&-baz";
query["bar"] = "bazinga";
builder.Query = query.ToString();
string url = builder.ToString();

will produce the desired outcome:

http://example.com/?foo=bar%3c%3e%26-baz&bar=bazinga

something you could feed to your HttpClient with confidence. The GetAsync function is used to retrieve asynchronous data.

Answered by Darin Dimitrov

Solution #2

If you don’t want to utilize System.Web in applications that don’t already use it, you can use System.Net.FormUrlEncodedContent Http’s and do something like this:

string query;
using(var content = new FormUrlEncodedContent(new KeyValuePair<string, string>[]{
    new KeyValuePair<string, string>("ham", "Glazed?"),
    new KeyValuePair<string, string>("x-men", "Wolverine + Logan"),
    new KeyValuePair<string, string>("Time", DateTime.UtcNow.ToString()),
})) {
    query = content.ReadAsStringAsync().Result;
}
string query;
using(var content = new FormUrlEncodedContent(new Dictionary<string, string>()
{
    { "ham", "Glaced?"},
    { "x-men", "Wolverine + Logan"},
    { "Time", DateTime.UtcNow.ToString() },
})) {
    query = content.ReadAsStringAsync().Result;
}

Answered by Rostov

Solution #3

You can use the QueryHelpers class in an ASP.NET Core project, which is available in the Microsoft.AspNetCore.WebUtilities namespace, or the.NET Standard 2.0 NuGet package for other consumers:

// using Microsoft.AspNetCore.WebUtilities;
var query = new Dictionary<string, string>
{
    ["foo"] = "bar",
    ["foo2"] = "bar2",
    // ...
};

var response = await client.GetAsync(QueryHelpers.AddQueryString("/api/", query));

Answered by Markus

Solution #4

TL;DR: don’t use the acceptable version because it’s broken when it comes to processing unicode characters, and never use the internal API.

With the acceptable approach, I’ve discovered an odd double encoding problem:

When dealing with characters that must be encoded, the accepted solution results in double encoding:

It’s a small repro, but it’s complete:

var builder = new UriBuilder
{
    Scheme = Uri.UriSchemeHttps,
    Port = -1,
    Host = "127.0.0.1",
    Path = "app"
};

NameValueCollection query = HttpUtility.ParseQueryString(builder.Query);

query["cyrillic"] = "кирилиця";

builder.Query = query.ToString();
Console.WriteLine(builder.Query); //query with cyrillic stuff UrlEncodedUnicode, and that's not what you want

var uri = builder.Uri; // creates new Uri using constructor which does encode and messes cyrillic parameter even more
Console.WriteLine(uri);

// this is still wrong:
var stringUri = builder.ToString(); // returns more 'correct' (still `UrlEncodedUnicode`, but at least once, not twice)
new HttpClient().GetStringAsync(stringUri); // this creates Uri object out of 'stringUri' so we still end up sending double encoded cyrillic text to server. Ouch!

Output:

?cyrillic=%u043a%u0438%u0440%u0438%u043b%u0438%u0446%u044f

https://127.0.0.1/app?cyrillic=%25u043a%25u0438%25u0440%25u0438%25u043b%25u0438%25u0446%25u044f

As you can see, it doesn’t matter if you use uribuilder or not. httpClient + ToString() uriBuilder or GetStringAsync(string). httpClient + Uri You end up sending a duplicate encoded parameter when you use GetStringAsync(Uri).

The following is an example:

var uri = new Uri(builder.ToString(), dontEscape: true);
new HttpClient().GetStringAsync(uri);

However, the Uri constructor is no longer in use.

P.S. On my latest.NET on Windows Server, the Uri constructor has a bool doc remark that states “obsolete, dontEscape is always false,” but it actually works.

It appears to be a new bug…

Even this is incorrect: it sends UrlEncodedUnicode to the server, rather than the UrlEncoded that the server wants.

Update: Another thing to note is that NameValueCollection performs UrlEncodeUnicode, which is deprecated and incompatible with ordinary url.encode/decode (see NameValueCollection to URL Query?).

The bottom thing is that if you apply this hack with NameValueCollection query = HttpUtility.ParseQueryString(builder.Query); your unicode query parameters will be messed up. Simply create a query by hand and assign it to UriBuilder. Query that does the appropriate encoding and then uses UriBuilder to acquire the Uri. Uri.

Use of code that isn’t designed to be used in this way is a prime example of self-harm.

Answered by illegal-immigrant

Solution #5

Flurl [disclaimer: I’m the author] is a fluent URL builder with an optional partner lib that turns it into a full-fledged REST client.

var result = await "https://api.com"
    // basic URL building:
    .AppendPathSegment("endpoint")
    .SetQueryParams(new {
        api_key = ConfigurationManager.AppSettings["SomeApiKey"],
        max_results = 20,
        q = "Don't worry, I'll get encoded!"
    })
    .SetQueryParams(myDictionary)
    .SetQueryParam("q", "overwrite q!")

    // extensions provided by Flurl.Http:
    .WithOAuthBearerToken("token")
    .GetJsonAsync<TResult>();

More information can be found in the documents. The complete package can be found on NuGet:

PM> Install-Package Flurl.Http

Alternatively, you may use the standalone URL builder:

PM> Install-Package Flurl

Answered by Todd Menier

Post is based on https://stackoverflow.com/questions/17096201/build-query-string-for-system-net-httpclient-get