c# Amazon Api request fail - c#

I'm trying to create a HTTP url request to get Amazon items by its ASIN Array. I'm using the same code in my Objective-c code for the same reason and it's work perfectly.
But i'm getting this messeage everytime i try to access the url in my chrome:
The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.
This is the code i'm using:
private void GetFinalUrlForAsinArray(ArrayList asinArr)
{
String timeStamp = GetTimeStamp();
String amazonAPIUrl = "http://webservices.amazon.com/onca/xml?";
ArrayList param = new ArrayList();
param.Add("AWSAccessKeyId=myawsaccesskeyid");
param.Add("AssociateTag=myassociatetag");
param.Add("IdType=ASIN");
param.Add(string.Join(",", asinArr.ToArray()));
param.Add("Operation=ItemLookup");
param.Add("ResponseGroup=ItemAttributes,Offers");
param.Add("Service=AWSECommerceService");
param.Add(String.Format("Timestamp={0}", timeStamp));
amazonAPIUrl += string.Join("&", param.ToArray());
string queryString = new System.Uri(amazonAPIUrl).Query;
var queryDictionary = HttpUtility.ParseQueryString(queryString);
ArrayList queryItemsNew = new ArrayList();
foreach (var query in queryDictionary)
{
String name = HttpUtility.UrlEncode((string)query);
String value = HttpUtility.UrlEncode((string)queryDictionary.Get((string)query));
queryItemsNew.Add(String.Format("{0}={1}", name,value));
}
String path = string.Join("&", queryItemsNew.ToArray());
String finalPath = String.Format("GET\nwebservices.amazon.com\n/onca/xml\n{0}",path);
string signature = HmacSha256Digest(finalPath);
String finalUrl = String.Format("http://webservices.amazon.com/onca/xml?{0}&Signature={1}", path, signature);
}
private String GetTimeStamp()
{
DateTime d = DateTime.UtcNow;
String str = d.ToString("yyyy-MM-dd''T''HH:mm:ss''Z''");
return str;
}
private static string HmacSha256Digest(string message)
{
UTF8Encoding encoding = new UTF8Encoding();
HMACSHA256 hmac = new HMACSHA256(encoding.GetBytes(mysecret));
string signature = Convert.ToBase64String(hmac.ComputeHash(encoding.GetBytes(message)));
String sigEncoded = Uri.EscapeDataString(signature);
return sigEncoded;
}

Having had a look at the API documentation it looks like you've missed the ItemId key here:
param.Add(string.Join(",", asinArr.ToArray()));
I'm guessing you meant it to be:
param.Add("ItemId=" + string.Join(",", asinArr.ToArray()));
It otherwise looks to comply with the spec, the only other thing I noted was the example had the URL encoding as uppercase i.e. %3A rather than the C# default %3a.

Related

Sending a Get request in C# with options doesn't work [duplicate]

If I wish to submit a http get request using System.Net.HttpClient there seems to be no api to add parameters, is this correct?
Is there any simple api available to build the query string that doesn't involve building a name value collection and url encoding those and then finally concatenating them?
I was hoping to use something like RestSharp's api (i.e AddParameter(..))
If I wish to submit a http get request using System.Net.HttpClient
there seems to be no api to add parameters, is this correct?
Yes.
Is there any simple api available to build the query string that
doesn't involve building a name value collection and url encoding
those and then finally concatenating them?
Sure:
var query = HttpUtility.ParseQueryString(string.Empty);
query["foo"] = "bar<>&-baz";
query["bar"] = "bazinga";
string queryString = query.ToString();
will give you the expected result:
foo=bar%3c%3e%26-baz&bar=bazinga
You might also find the UriBuilder class useful:
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 give you the expected result:
http://example.com/?foo=bar%3c%3e%26-baz&bar=bazinga
that you could more than safely feed to your HttpClient.GetAsync method.
For those who do not want to include System.Web in projects that don't already use it, you can use FormUrlEncodedContent from System.Net.Http and do something like the following:
keyvaluepair version
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;
}
dictionary version
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;
}
In a ASP.NET Core project you can use the QueryHelpers class, available in the Microsoft.AspNetCore.WebUtilities namespace for ASP.NET Core, 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));
TL;DR: do not use accepted version as It's completely broken in relation to handling unicode characters, and never use internal API
I've actually found weird double encoding issue with the accepted solution:
So, If you're dealing with characters which need to be encoded, accepted solution leads to double encoding:
query parameters are auto encoded by using NameValueCollection indexer (and this uses UrlEncodeUnicode, not regular expected UrlEncode(!))
Then, when you call uriBuilder.Uri it creates new Uri using constructor which does encoding one more time (normal url encoding)
That cannot be avoided by doing uriBuilder.ToString() (even though this returns correct Uri which IMO is at least inconsistency, maybe a bug, but that's another question) and then using HttpClient method accepting string - client still creates Uri out of your passed string like this: new Uri(uri, UriKind.RelativeOrAbsolute)
Small, but full repro:
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 may see, no matter if you do uribuilder.ToString() + httpClient.GetStringAsync(string) or uriBuilder.Uri + httpClient.GetStringAsync(Uri) you end up sending double encoded parameter
Fixed example could be:
var uri = new Uri(builder.ToString(), dontEscape: true);
new HttpClient().GetStringAsync(uri);
But this uses obsolete Uri constructor
P.S on my latest .NET on Windows Server, Uri constructor with bool doc comment says "obsolete, dontEscape is always false", but actually works as expected (skips escaping)
So It looks like another bug...
And even this is plain wrong - it send UrlEncodedUnicode to server, not just UrlEncoded what server expects
Update: one more thing is, NameValueCollection actually does UrlEncodeUnicode, which is not supposed to be used anymore and is incompatible with regular url.encode/decode (see NameValueCollection to URL Query?).
So the bottom line is: never use this hack with NameValueCollection query = HttpUtility.ParseQueryString(builder.Query); as it will mess your unicode query parameters. Just build query manually and assign it to UriBuilder.Query which will do necessary encoding and then get Uri using UriBuilder.Uri.
Prime example of hurting yourself by using code which is not supposed to be used like this
You might want to check out Flurl [disclosure: I'm the author], a fluent URL builder with optional companion lib that extends it into a full-blown 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>();
Check out the docs for more details. The full package is available on NuGet:
PM> Install-Package Flurl.Http
or just the stand-alone URL builder:
PM> Install-Package Flurl
Along the same lines as Rostov's post, if you do not want to include a reference to System.Web in your project, you can use FormDataCollection from System.Net.Http.Formatting and do something like the following:
Using System.Net.Http.Formatting.FormDataCollection
var parameters = new Dictionary<string, string>()
{
{ "ham", "Glaced?" },
{ "x-men", "Wolverine + Logan" },
{ "Time", DateTime.UtcNow.ToString() },
};
var query = new FormDataCollection(parameters).ReadAsNameValueCollection().ToString();
Since I have to reuse this few time, I came up with this class that simply help to abstract how the query string is composed.
public class UriBuilderExt
{
private NameValueCollection collection;
private UriBuilder builder;
public UriBuilderExt(string uri)
{
builder = new UriBuilder(uri);
collection = System.Web.HttpUtility.ParseQueryString(string.Empty);
}
public void AddParameter(string key, string value) {
collection.Add(key, value);
}
public Uri Uri{
get
{
builder.Query = collection.ToString();
return builder.Uri;
}
}
}
The use will be simplify to something like this:
var builder = new UriBuilderExt("http://example.com/");
builder.AddParameter("foo", "bar<>&-baz");
builder.AddParameter("bar", "second");
var uri = builder.Uri;
that will return the uri:
http://example.com/?foo=bar%3c%3e%26-baz&bar=second
Good part of accepted answer, modified to use UriBuilder.Uri.ParseQueryString() instead of HttpUtility.ParseQueryString():
var builder = new UriBuilder("http://example.com");
var query = builder.Uri.ParseQueryString();
query["foo"] = "bar<>&-baz";
query["bar"] = "bazinga";
builder.Query = query.ToString();
string url = builder.ToString();
Darin offered an interesting and clever solution, and here is something that may be another option:
public class ParameterCollection
{
private Dictionary<string, string> _parms = new Dictionary<string, string>();
public void Add(string key, string val)
{
if (_parms.ContainsKey(key))
{
throw new InvalidOperationException(string.Format("The key {0} already exists.", key));
}
_parms.Add(key, val);
}
public override string ToString()
{
var server = HttpContext.Current.Server;
var sb = new StringBuilder();
foreach (var kvp in _parms)
{
if (sb.Length > 0) { sb.Append("&"); }
sb.AppendFormat("{0}={1}",
server.UrlEncode(kvp.Key),
server.UrlEncode(kvp.Value));
}
return sb.ToString();
}
}
and so when using it, you might do this:
var parms = new ParameterCollection();
parms.Add("key", "value");
var url = ...
url += "?" + parms;
The RFC 6570 URI Template library I'm developing is capable of performing this operation. All encoding is handled for you in accordance with that RFC. At the time of this writing, a beta release is available and the only reason it's not considered a stable 1.0 release is the documentation doesn't fully meet my expectations (see issues #17, #18, #32, #43).
You could either build a query string alone:
UriTemplate template = new UriTemplate("{?params*}");
var parameters = new Dictionary<string, string>
{
{ "param1", "value1" },
{ "param2", "value2" },
};
Uri relativeUri = template.BindByName(parameters);
Or you could build a complete URI:
UriTemplate template = new UriTemplate("path/to/item{?params*}");
var parameters = new Dictionary<string, string>
{
{ "param1", "value1" },
{ "param2", "value2" },
};
Uri baseAddress = new Uri("http://www.example.com");
Uri relativeUri = template.BindByName(baseAddress, parameters);
Or simply using my Uri extension
Code
public static Uri AttachParameters(this Uri uri, NameValueCollection parameters)
{
var stringBuilder = new StringBuilder();
string str = "?";
for (int index = 0; index < parameters.Count; ++index)
{
stringBuilder.Append(str + parameters.AllKeys[index] + "=" + parameters[index]);
str = "&";
}
return new Uri(uri + stringBuilder.ToString());
}
Usage
Uri uri = new Uri("http://www.example.com/index.php").AttachParameters(new NameValueCollection
{
{"Bill", "Gates"},
{"Steve", "Jobs"}
});
Result
http://www.example.com/index.php?Bill=Gates&Steve=Jobs
To avoid double encoding issue described in taras.roshko's answer and to keep possibility to easily work with query parameters, you can use uriBuilder.Uri.ParseQueryString() instead of HttpUtility.ParseQueryString().
Thanks to "Darin Dimitrov", This is the extension methods.
public static partial class Ext
{
public static Uri GetUriWithparameters(this Uri uri,Dictionary<string,string> queryParams = null,int port = -1)
{
var builder = new UriBuilder(uri);
builder.Port = port;
if(null != queryParams && 0 < queryParams.Count)
{
var query = HttpUtility.ParseQueryString(builder.Query);
foreach(var item in queryParams)
{
query[item.Key] = item.Value;
}
builder.Query = query.ToString();
}
return builder.Uri;
}
public static string GetUriWithparameters(string uri,Dictionary<string,string> queryParams = null,int port = -1)
{
var builder = new UriBuilder(uri);
builder.Port = port;
if(null != queryParams && 0 < queryParams.Count)
{
var query = HttpUtility.ParseQueryString(builder.Query);
foreach(var item in queryParams)
{
query[item.Key] = item.Value;
}
builder.Query = query.ToString();
}
return builder.Uri.ToString();
}
}
HttpClient client = new HttpClient();
var uri = Environment.GetEnvironmentVariable("URL of Api");
var requesturi = QueryHelpers.AddQueryString(uri, "parameter_name",parameter_value);
client.BaseAddress = new Uri(requesturi);
And then you can add request headers also eg:
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("x-api-key", secretValue);
response syntax eg:
HttpResponseMessage response = client.GetAsync(requesturi).Result;
Hope it will work for you.
My answer doesn't globally differ from the accepted/other answers. I just tried to create an extension method for the Uri type, which takes variable number of parameters.
public static class UriExtensions
{
public static Uri AddParameter(this Uri url, params (string Name, string Value)[] #params)
{
if (!#params.Any())
{
return url;
}
UriBuilder uriBuilder = new(url);
NameValueCollection query = HttpUtility.ParseQueryString(uriBuilder.Query);
foreach (var param in #params)
{
query[param.Name] = param.Value.Trim();
}
uriBuilder.Query = query.ToString();
return uriBuilder.Uri;
}
}
Usage example:
var uri = new Uri("http://someuri.com")
.AddParameter(
("p1.name", "p1.value"),
("p2.name", "p2.value"),
("p3.name", "p3.value"));
I couldn't find a better solution than creating a extension method to convert a Dictionary to QueryStringFormat. The solution proposed by Waleed A.K. is good as well.
Follow my solution:
Create the extension method:
public static class DictionaryExt
{
public static string ToQueryString<TKey, TValue>(this Dictionary<TKey, TValue> dictionary)
{
return ToQueryString<TKey, TValue>(dictionary, "?");
}
public static string ToQueryString<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, string startupDelimiter)
{
string result = string.Empty;
foreach (var item in dictionary)
{
if (string.IsNullOrEmpty(result))
result += startupDelimiter; // "?";
else
result += "&";
result += string.Format("{0}={1}", item.Key, item.Value);
}
return result;
}
}
And them:
var param = new Dictionary<string, string>
{
{ "param1", "value1" },
{ "param2", "value2" },
};
param.ToQueryString(); //By default will add (?) question mark at begining
//"?param1=value1&param2=value2"
param.ToQueryString("&"); //Will add (&)
//"&param1=value1&param2=value2"
param.ToQueryString(""); //Won't add anything
//"param1=value1&param2=value2"

Retrieving data from an API

The first part of the program is to retrieve the employee user ID (or signature) from an API URL once the name has been entered. (Which I have done)
The second part, the user will enter a specific "to" and "from" date.
Using the signature obtained from the first part and the dates that the user enters, the program should pass this information to an API address and obtain information accordingly.
My question is that I am not sure how to pass the obtained signature to the new API address + the "to" and "from" date.
The first part of the program to retrieve the signature (works perfectly):
namespace TimeSheets_Try_11.Controllers
{
class WebAPI
{
public string Getsignature(string name)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
var cookies = FullWebBrowserCookie.GetCookieInternal(new Uri(StaticStrings.UrlIora), false);
WebClient wc = new WebClient();
wc.Encoding = System.Text.Encoding.UTF8;
wc.Headers.Add("Cookie:" + cookies);
wc.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
wc.UseDefaultCredentials = true;
string uri = "";
uri = StaticStrings.UrlIora + name;
var response = wc.DownloadString(uri);
var status = JsonConvert.DeserializeObject<List<Employeename>>(response);
string signame = status.Select(js => js.signature).First();
return signame;
}
The second part that I have written so far:
public string[] GetTime(double fromDate, double toDate, string username)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
var cookies = FullWebBrowserCookie.GetCookieInternal(new Uri(StaticStrings.UrlNcert), false);
WebClient wc = new WebClient();
wc.Encoding = System.Text.Encoding.UTF8;
wc.Headers.Add("Cookie:" + cookies);
wc.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
wc.UseDefaultCredentials = true;
string url = "";
url = StaticStrings.UrlNcert + username + "&fromDate=" + fromDate + "&toDate=" + toDate;
var respons = wc.DownloadString(url);
OracleHour ndata = JsonConvert.DeserializeObject<OracleHour>(respons);
var Get_Odnum = ndata.orderNumber;
var Dt_Work = ndata.dateOfWork;
var hrType = ndata.hourType;
var hr = ndata.hours;
var des = ndata.description;
var surname = ndata.surveyor;
string[] myncertdata = { Get_Odnum, Dt_Work.ToString(), hrType, hr.ToString(), des, surname };
return myncertdata;
}
}
}
The API strings:
namespace TimeSheets_Try_11.Controllers
{
class StaticStrings
{
public static string UrlIora = "https://iora.dnvgl.com/api/dictionary/employee/";
public static string UrlNcert = "https://cmcservices.dnvgl.com/Finance/api/oracleReportingCost?user=VERIT" + #"\";
}
}
For example if we are using the name "Jane Dow" from the date 9/22/20 - 9/29/20, the api strings will be
UrlIora = "https://iora.dnvgl.com/api/dictionary/employee/Jane
UrlNcert = "https://cmcservices.dnvgl.com/Finance/api/oracleReportingCost?user=VERIT\JDOW&fromDate=2020-09-22&toDate=2020-09-29"
Trivial way - change UrlNcert to url without query at first:
class StaticStrings
{
public static string UrlIora = "https://iora.dnvgl.com/api/dictionary/employee/";
public static string UrlNcert = "https://cmcservices.dnvgl.com/Finance/api/oracleReportingCost";
}
Then in your api call get values for username, fromDate and toDate and use string interpolation.
var url = $"{StaticStrings.UrlNcert}?user={username}&fromDate={fromDate:yyyy-MM-dd}&toDate={toDate:yyyy-MM-dd}";
If you want more complex way, check UriBuilder

Xamarin c# for Android : creating a json string?

Having problems with this code ..
GlodalVariables.SoftID = "55";
WebClient client = new WebClient ();
Uri uri = new Uri ("http://www.example.com/CreateEntry.php");
string folder = System.Environment.GetFolderPath (System.Environment.SpecialFolder.Personal);
Android.Content.Context myContext = Android.App.Application.Context;
try{
string smsUri = System.IO.Path.Combine (folder, "SL.db");
string myjson = "";
SQLiteDatabase Mydb = SQLiteDatabase.OpenDatabase(smsUri , null, DatabaseOpenFlags.OpenReadwrite );
ICursor SMScursor = Mydb.Query ("MySMSLog", null,null, null,null, null ,"TheDate ASC");
MySMSLog test = new MySMSLog() ;
if (SMScursor.MoveToFirst ()) {
while (!SMScursor.IsAfterLast){
string number = SMScursor.GetString(SMScursor.GetColumnIndex("TheNumber"));
string name = SMScursor.GetString(SMScursor.GetColumnIndex("TheName"));
string date = SMScursor.GetString(SMScursor.GetColumnIndex("TheDate"));
string direction = SMScursor.GetString(SMScursor.GetColumnIndex("TheDirection"));
string text = SMScursor.GetString(SMScursor.GetColumnIndex("TheText"));
string id = SMScursor.GetString(SMScursor.GetColumnIndex("Id"));
test.Id = int.Parse(id);
test.TheDate = date;
test.TheDirection = direction ;
test.TheName = name;
test.TheNumber = number;
test.TheText = text;
string output = Newtonsoft.Json.JsonConvert.SerializeObject (test);
myjson = myjson + output + " ";
SMScursor.MoveToNext ();
}
}
System.Console.WriteLine (myjson );
System.Console.WriteLine();
SMScursor.Close ();
When i Copy the complete json string into a json test site (http://jsonlint.com/)
It tells me the sting is invalid ...
I'm getting all the record rows and putting them into a single json string before pushing them over to the server..
Shouldn't you get the result in an array form? So the final json should look like :
[{json1},{json2}..]
Probably if you have just {json1} {json2} this is not a valid object.
Could an alternative solution be to build a collection of "MySMSLog" objects, then serialise the collection throught JSonConvert? That way you don't need to worry about getting the structure correct through your own string manipulation.
This would most likely also future proof your code on the ridiculously outlandish chance that JSON standards change, as the NewtonSoft library would be updated as well.

Build query string for System.Net.HttpClient get

If I wish to submit a http get request using System.Net.HttpClient there seems to be no api to add parameters, is this correct?
Is there any simple api available to build the query string that doesn't involve building a name value collection and url encoding those and then finally concatenating them?
I was hoping to use something like RestSharp's api (i.e AddParameter(..))
If I wish to submit a http get request using System.Net.HttpClient
there seems to be no api to add parameters, is this correct?
Yes.
Is there any simple api available to build the query string that
doesn't involve building a name value collection and url encoding
those and then finally concatenating them?
Sure:
var query = HttpUtility.ParseQueryString(string.Empty);
query["foo"] = "bar<>&-baz";
query["bar"] = "bazinga";
string queryString = query.ToString();
will give you the expected result:
foo=bar%3c%3e%26-baz&bar=bazinga
You might also find the UriBuilder class useful:
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 give you the expected result:
http://example.com/?foo=bar%3c%3e%26-baz&bar=bazinga
that you could more than safely feed to your HttpClient.GetAsync method.
For those who do not want to include System.Web in projects that don't already use it, you can use FormUrlEncodedContent from System.Net.Http and do something like the following:
keyvaluepair version
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;
}
dictionary version
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;
}
In a ASP.NET Core project you can use the QueryHelpers class, available in the Microsoft.AspNetCore.WebUtilities namespace for ASP.NET Core, 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));
TL;DR: do not use accepted version as It's completely broken in relation to handling unicode characters, and never use internal API
I've actually found weird double encoding issue with the accepted solution:
So, If you're dealing with characters which need to be encoded, accepted solution leads to double encoding:
query parameters are auto encoded by using NameValueCollection indexer (and this uses UrlEncodeUnicode, not regular expected UrlEncode(!))
Then, when you call uriBuilder.Uri it creates new Uri using constructor which does encoding one more time (normal url encoding)
That cannot be avoided by doing uriBuilder.ToString() (even though this returns correct Uri which IMO is at least inconsistency, maybe a bug, but that's another question) and then using HttpClient method accepting string - client still creates Uri out of your passed string like this: new Uri(uri, UriKind.RelativeOrAbsolute)
Small, but full repro:
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 may see, no matter if you do uribuilder.ToString() + httpClient.GetStringAsync(string) or uriBuilder.Uri + httpClient.GetStringAsync(Uri) you end up sending double encoded parameter
Fixed example could be:
var uri = new Uri(builder.ToString(), dontEscape: true);
new HttpClient().GetStringAsync(uri);
But this uses obsolete Uri constructor
P.S on my latest .NET on Windows Server, Uri constructor with bool doc comment says "obsolete, dontEscape is always false", but actually works as expected (skips escaping)
So It looks like another bug...
And even this is plain wrong - it send UrlEncodedUnicode to server, not just UrlEncoded what server expects
Update: one more thing is, NameValueCollection actually does UrlEncodeUnicode, which is not supposed to be used anymore and is incompatible with regular url.encode/decode (see NameValueCollection to URL Query?).
So the bottom line is: never use this hack with NameValueCollection query = HttpUtility.ParseQueryString(builder.Query); as it will mess your unicode query parameters. Just build query manually and assign it to UriBuilder.Query which will do necessary encoding and then get Uri using UriBuilder.Uri.
Prime example of hurting yourself by using code which is not supposed to be used like this
You might want to check out Flurl [disclosure: I'm the author], a fluent URL builder with optional companion lib that extends it into a full-blown 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>();
Check out the docs for more details. The full package is available on NuGet:
PM> Install-Package Flurl.Http
or just the stand-alone URL builder:
PM> Install-Package Flurl
Along the same lines as Rostov's post, if you do not want to include a reference to System.Web in your project, you can use FormDataCollection from System.Net.Http.Formatting and do something like the following:
Using System.Net.Http.Formatting.FormDataCollection
var parameters = new Dictionary<string, string>()
{
{ "ham", "Glaced?" },
{ "x-men", "Wolverine + Logan" },
{ "Time", DateTime.UtcNow.ToString() },
};
var query = new FormDataCollection(parameters).ReadAsNameValueCollection().ToString();
Since I have to reuse this few time, I came up with this class that simply help to abstract how the query string is composed.
public class UriBuilderExt
{
private NameValueCollection collection;
private UriBuilder builder;
public UriBuilderExt(string uri)
{
builder = new UriBuilder(uri);
collection = System.Web.HttpUtility.ParseQueryString(string.Empty);
}
public void AddParameter(string key, string value) {
collection.Add(key, value);
}
public Uri Uri{
get
{
builder.Query = collection.ToString();
return builder.Uri;
}
}
}
The use will be simplify to something like this:
var builder = new UriBuilderExt("http://example.com/");
builder.AddParameter("foo", "bar<>&-baz");
builder.AddParameter("bar", "second");
var uri = builder.Uri;
that will return the uri:
http://example.com/?foo=bar%3c%3e%26-baz&bar=second
Good part of accepted answer, modified to use UriBuilder.Uri.ParseQueryString() instead of HttpUtility.ParseQueryString():
var builder = new UriBuilder("http://example.com");
var query = builder.Uri.ParseQueryString();
query["foo"] = "bar<>&-baz";
query["bar"] = "bazinga";
builder.Query = query.ToString();
string url = builder.ToString();
Darin offered an interesting and clever solution, and here is something that may be another option:
public class ParameterCollection
{
private Dictionary<string, string> _parms = new Dictionary<string, string>();
public void Add(string key, string val)
{
if (_parms.ContainsKey(key))
{
throw new InvalidOperationException(string.Format("The key {0} already exists.", key));
}
_parms.Add(key, val);
}
public override string ToString()
{
var server = HttpContext.Current.Server;
var sb = new StringBuilder();
foreach (var kvp in _parms)
{
if (sb.Length > 0) { sb.Append("&"); }
sb.AppendFormat("{0}={1}",
server.UrlEncode(kvp.Key),
server.UrlEncode(kvp.Value));
}
return sb.ToString();
}
}
and so when using it, you might do this:
var parms = new ParameterCollection();
parms.Add("key", "value");
var url = ...
url += "?" + parms;
The RFC 6570 URI Template library I'm developing is capable of performing this operation. All encoding is handled for you in accordance with that RFC. At the time of this writing, a beta release is available and the only reason it's not considered a stable 1.0 release is the documentation doesn't fully meet my expectations (see issues #17, #18, #32, #43).
You could either build a query string alone:
UriTemplate template = new UriTemplate("{?params*}");
var parameters = new Dictionary<string, string>
{
{ "param1", "value1" },
{ "param2", "value2" },
};
Uri relativeUri = template.BindByName(parameters);
Or you could build a complete URI:
UriTemplate template = new UriTemplate("path/to/item{?params*}");
var parameters = new Dictionary<string, string>
{
{ "param1", "value1" },
{ "param2", "value2" },
};
Uri baseAddress = new Uri("http://www.example.com");
Uri relativeUri = template.BindByName(baseAddress, parameters);
Or simply using my Uri extension
Code
public static Uri AttachParameters(this Uri uri, NameValueCollection parameters)
{
var stringBuilder = new StringBuilder();
string str = "?";
for (int index = 0; index < parameters.Count; ++index)
{
stringBuilder.Append(str + parameters.AllKeys[index] + "=" + parameters[index]);
str = "&";
}
return new Uri(uri + stringBuilder.ToString());
}
Usage
Uri uri = new Uri("http://www.example.com/index.php").AttachParameters(new NameValueCollection
{
{"Bill", "Gates"},
{"Steve", "Jobs"}
});
Result
http://www.example.com/index.php?Bill=Gates&Steve=Jobs
To avoid double encoding issue described in taras.roshko's answer and to keep possibility to easily work with query parameters, you can use uriBuilder.Uri.ParseQueryString() instead of HttpUtility.ParseQueryString().
Thanks to "Darin Dimitrov", This is the extension methods.
public static partial class Ext
{
public static Uri GetUriWithparameters(this Uri uri,Dictionary<string,string> queryParams = null,int port = -1)
{
var builder = new UriBuilder(uri);
builder.Port = port;
if(null != queryParams && 0 < queryParams.Count)
{
var query = HttpUtility.ParseQueryString(builder.Query);
foreach(var item in queryParams)
{
query[item.Key] = item.Value;
}
builder.Query = query.ToString();
}
return builder.Uri;
}
public static string GetUriWithparameters(string uri,Dictionary<string,string> queryParams = null,int port = -1)
{
var builder = new UriBuilder(uri);
builder.Port = port;
if(null != queryParams && 0 < queryParams.Count)
{
var query = HttpUtility.ParseQueryString(builder.Query);
foreach(var item in queryParams)
{
query[item.Key] = item.Value;
}
builder.Query = query.ToString();
}
return builder.Uri.ToString();
}
}
HttpClient client = new HttpClient();
var uri = Environment.GetEnvironmentVariable("URL of Api");
var requesturi = QueryHelpers.AddQueryString(uri, "parameter_name",parameter_value);
client.BaseAddress = new Uri(requesturi);
And then you can add request headers also eg:
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("x-api-key", secretValue);
response syntax eg:
HttpResponseMessage response = client.GetAsync(requesturi).Result;
Hope it will work for you.
My answer doesn't globally differ from the accepted/other answers. I just tried to create an extension method for the Uri type, which takes variable number of parameters.
public static class UriExtensions
{
public static Uri AddParameter(this Uri url, params (string Name, string Value)[] #params)
{
if (!#params.Any())
{
return url;
}
UriBuilder uriBuilder = new(url);
NameValueCollection query = HttpUtility.ParseQueryString(uriBuilder.Query);
foreach (var param in #params)
{
query[param.Name] = param.Value.Trim();
}
uriBuilder.Query = query.ToString();
return uriBuilder.Uri;
}
}
Usage example:
var uri = new Uri("http://someuri.com")
.AddParameter(
("p1.name", "p1.value"),
("p2.name", "p2.value"),
("p3.name", "p3.value"));
I couldn't find a better solution than creating a extension method to convert a Dictionary to QueryStringFormat. The solution proposed by Waleed A.K. is good as well.
Follow my solution:
Create the extension method:
public static class DictionaryExt
{
public static string ToQueryString<TKey, TValue>(this Dictionary<TKey, TValue> dictionary)
{
return ToQueryString<TKey, TValue>(dictionary, "?");
}
public static string ToQueryString<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, string startupDelimiter)
{
string result = string.Empty;
foreach (var item in dictionary)
{
if (string.IsNullOrEmpty(result))
result += startupDelimiter; // "?";
else
result += "&";
result += string.Format("{0}={1}", item.Key, item.Value);
}
return result;
}
}
And them:
var param = new Dictionary<string, string>
{
{ "param1", "value1" },
{ "param2", "value2" },
};
param.ToQueryString(); //By default will add (?) question mark at begining
//"?param1=value1&param2=value2"
param.ToQueryString("&"); //Will add (&)
//"&param1=value1&param2=value2"
param.ToQueryString(""); //Won't add anything
//"param1=value1&param2=value2"

Replace item in querystring

I have a URL that also might have a query string part, the query string might be empty or have multiple items.
I want to replace one of the items in the query string or add it if the item doesn't already exists.
I have an URI object with the complete URL.
My first idea was to use regex and some string magic, that should do it.
But it seems a bit shaky, perhaps the framework has some query string builder class?
I found this was a more elegant solution
var qs = HttpUtility.ParseQueryString(Request.QueryString.ToString());
qs.Set("item", newItemValue);
Console.WriteLine(qs.ToString());
Lets have this url:
https://localhost/video?param1=value1
At first update specific query string param to new value:
var uri = new Uri("https://localhost/video?param1=value1");
var qs = HttpUtility.ParseQueryString(uri.Query);
qs.Set("param1", "newValue2");
Next create UriBuilder and update Query property to produce new uri with changed param value.
var uriBuilder = new UriBuilder(uri);
uriBuilder.Query = qs.ToString();
var newUri = uriBuilder.Uri;
Now you have in newUri this value:
https://localhost/video?param1=newValue2
Maybe you could use the System.UriBuilder class. It has a Query property.
I use following method:
public static string replaceQueryString(System.Web.HttpRequest request, string key, string value)
{
System.Collections.Specialized.NameValueCollection t = HttpUtility.ParseQueryString(request.Url.Query);
t.Set(key, value);
return t.ToString();
}
string link = page.Request.Url.ToString();
if(page.Request.Url.Query == "")
link += "?pageIndex=" + pageIndex;
else if (page.Request.QueryString["pageIndex"] != "")
{
var idx = page.Request.QueryString["pageIndex"];
link = link.Replace("pageIndex=" + idx, "pageIndex=" + pageIndex);
}
else
link += "&pageIndex=" + pageIndex;
This seems to work really well.
No, the framework doesn't have any existing QueryStringBuilder class, but usually the querystring information in a HTTP request is available as an iterable and searchable NameValueCollection via the Request.Querystring property.
Since you are starting off with a Uri object, however, you will need to obtain the querystring portion using the Query property of the Uri object. This will yield a string of the form:
Uri myURI = new Uri("http://www.mywebsite.com/page.aspx?Val1=A&Val2=B&Val3=C");
string querystring = myURI.Query;
// Outputs: "?Val1=A&Val2=B&Val3=C". Note the ? prefix!
Console.WriteLine(querystring);
You can then split this string on the ampersand character to differentiate it into different querystring parameters-value pairs. Then again split each parameter on the "=" character to differentiate it into a key and value.
Since your final goal is to search for a particular querystring key and if necessary create it, you should try to (re)create a collection (preferably, a generic one) that allows you easily search in the collection, similar to the facility provided by the NameValueCollection class.
I used the following code to append/replace the value of a parameter in the current request URL:
public static string CurrentUrlWithParam(this UrlHelper helper, string paramName, string paramValue)
{
var url = helper.RequestContext.HttpContext.Request.Url;
var sb = new StringBuilder();
sb.AppendFormat("{0}://{1}{2}{3}",
url.Scheme,
url.Host,
url.IsDefaultPort ? "" : ":" + url.Port,
url.LocalPath);
var isFirst = true;
if (!String.IsNullOrWhiteSpace(url.Query))
{
var queryStrings = url.Query.Split(new[] { '?', ';' });
foreach (var queryString in queryStrings)
{
if (!String.IsNullOrWhiteSpace(queryString) && !queryString.StartsWith(paramName + "="))
{
sb.AppendFormat("{0}{1}", isFirst ? "?" : ";", queryString);
isFirst = false;
}
}
}
sb.AppendFormat("{0}{1}={2}", isFirst ? "?" : ";", paramName, paramValue);
return sb.ToString();
}
Maybe this helps others when finding this topic.
Update:
Just saw the hint about UriBuilder and did a second version using UriBuilder, StringBuilder and Linq:
public static string CurrentUrlWithParam(this UrlHelper helper, string paramName, string paramValue)
{
var url = helper.RequestContext.HttpContext.Request.Url;
var ub = new UriBuilder(url.Scheme, url.Host, url.Port, url.LocalPath);
// Query string
var sb = new StringBuilder();
var isFirst = true;
if (!String.IsNullOrWhiteSpace(url.Query))
{
var queryStrings = url.Query.Split(new[] { '?', ';' });
foreach (var queryString in queryStrings.Where(queryString => !String.IsNullOrWhiteSpace(queryString) && !queryString.StartsWith(paramName + "=")))
{
sb.AppendFormat("{0}{1}", isFirst ? "" : ";", queryString);
isFirst = false;
}
}
sb.AppendFormat("{0}{1}={2}", isFirst ? "" : ";", paramName, paramValue);
ub.Query = sb.ToString();
return ub.ToString();
}
I agree with Cerebrus. Sticking to the KISS principle, you have the querystring,
string querystring = myURI.Query;
you know what you are looking for and what you want to replace it with.
So use something like this:-
if (querystring == "")
myURI.Query += "?" + replacestring;
else
querystring.replace (searchstring, replacestring); // not too sure of syntax !!
I answered a similar question a while ago. Basically, the best way would be to use the class HttpValueCollection, which the QueryString property actually is, unfortunately it is internal in the .NET framework.
You could use Reflector to grab it (and place it into your Utils class). This way you could manipulate the query string like a NameValueCollection, but with all the url encoding/decoding issues taken care for you.
HttpValueCollection extends NameValueCollection, and has a constructor that takes an encoded query string (ampersands and question marks included), and it overrides a ToString() method to later rebuild the query string from the underlying collection.
public class QueryParams : Dictionary<string,string>
{
private Uri originolUrl;
private Uri ammendedUrl;
private string schemeName;
private string hostname;
private string path;
public QueryParams(Uri url)
{
this.originolUrl = url;
schemeName = url.Scheme;
hostname = url.Host;
path = url.AbsolutePath;
//check uri to see if it has a query
if (url.Query.Count() > 1)
{
//we grab the query and strip of the question mark as we do not want it attached
string query = url.Query.TrimStart("?".ToArray());
//we grab each query and place them into an array
string[] parms = query.Split("&".ToArray());
foreach (string str in parms)
{
// we split each query into two strings(key) and (value) and place into array
string[] param = str.Split("=".ToArray());
//we add the strings to this dictionary
this.Add(param[0], param[1]);
}
}
}
public QueryParams Set(string paramName, string value)
{
if(this.ContainsKey(paramName))
{
//if key exists change value
this[paramName] = value;
return (this);
}
else
{
this.Add(paramName, value);
return this;
}
}
public QueryParams Set(string paramName, int value)
{
if (this.ContainsKey(paramName))
{
//if key exists change value
this[paramName] = value.ToString();
return (this);
}
else
{
this.Add(paramName, value);
return this;
}
}
public void Add(string key, int value)
{
//overload, adds a new keypair
string strValue = value.ToString();
this.Add(key, strValue);
}
public override string ToString()
{
StringBuilder queryString = new StringBuilder();
foreach (KeyValuePair<string, string> pair in this)
{
//we recreate the query from each keypair
queryString.Append(pair.Key + "=" + pair.Value + "&");
}
//trim the end of the query
string modifiedQuery = queryString.ToString().TrimEnd("&".ToArray());
if (this.Count() > 0)
{
UriBuilder uriBuild = new UriBuilder(schemeName, hostname);
uriBuild.Path = path;
uriBuild.Query = modifiedQuery;
ammendedUrl = uriBuild.Uri;
return ammendedUrl.AbsoluteUri;
}
else
{
return originolUrl.ToString();
}
}
public Uri ToUri()
{
this.ToString();
return ammendedUrl;
}
}
}

Categories

Resources