24

I am working on an asp.net mvc 4 web application. and i am using .net 4.5. now i have the following WebClient() class:

using (var client = new WebClient())
{
    var query = HttpUtility.ParseQueryString(string.Empty);

    query["model"] = Model;
    //code goes here for other parameters....

    string apiurl = System.Web.Configuration.WebConfigurationManager.AppSettings["ApiURL"];
    var url = new UriBuilder(apiurl);
    url.Query = query.ToString();

    string xml = client.DownloadString(url.ToString());
    XmlDocument doc = new XmlDocument();
    //code goes here ....

}

now i have noted a problem when one of the parameters contain non-ASCII charterers such as £, ¬, etc....

now the final query will have any non-ASCII characters (such as £) encoded wrongly (as %u00a3). i read about this problem and seems i can replace :-

url.Query = query.ToString();

with

url.Query = ri.EscapeUriString(HttpUtility.UrlDecode(query.ToString()));    

now using the later approach will encode £ as %C2%A3 which is the correct encoded value.

but the problem i am facing with url.Query = Uri.EscapeUriString(HttpUtility.UrlDecode(query.ToString())); in that case one of the parameters contains & then the url will have the following format &operation=AddAsset&assetName=&.... so it will assume that I am passing empty assetName parameter not value =&??

EDIT

Let me summarize my problem again. I want to be able to pass the following 3 things inside my URL to a third part API :

  1. Standard characters such as A,B ,a ,b ,1, 2, 3 ...

  2. Non-ASCII characters such as £,¬ .

  3. and also special characters that are used in url encoding such as & , + .

now i tried the following 2 approaches :

Approach A:

using (var client = new WebClient())
{
    var query = HttpUtility.ParseQueryString(string.Empty);

    query["model"] = Model;
    //code goes here for other parameters....

    string apiurl = System.Web.Configuration.WebConfigurationManager.AppSettings["ApiURL"];
    var url = new UriBuilder(apiurl);
    url.Query = query.ToString();

    string xml = client.DownloadString(url.ToString());
    XmlDocument doc = new XmlDocument();
    //code goes here ....

}

In this approach i can pass values such as & ,+ since they are going to be url encoded ,,but if i want to pass non-ASCII characters they will be encoded using ISO-8859-1 ... so if i have £ value , my above code will encoded as %u00a3 and it will be saved inside the 3rd party API as %u00a3 instead of £.

Approach B :

I use :

url.Query = Uri.EscapeUriString(HttpUtility.UrlDecode(query.ToString())); 

instead of

url.Query = query.ToString();

now I can pass non-ASCII characters such as £ since they will be encoded correctly using UTF8 instead of ISO-8859-1. but i can not pass values such as & because my url will be read wrongly by the 3rd party API.. for example if I want to pass assetName=& my url will look as follow:

&operation=Add&assetName=&

so the third part API will assume I am passing empty assetName, while I am trying to pass its value as &...

so not sure how I can pass both non-ASCII characters + characters such as &, + ????

varocarbas
  • 12,354
  • 4
  • 26
  • 37
John John
  • 1
  • 72
  • 238
  • 501

5 Answers5

9

You could use System.Net.Http.FormUrlEncodedContent instead.

This works with a Dictionary for the Name/Value pairing and the Dictionary, unlike the NameValueCollection, does not "incorrectly" map characters such as £ to an unhelpful escaping (%u00a3, in your case).

Instead, the FormUrlEncodedContent can take a dictionary in its constructor. When you read the string out of it, it will have properly urlencoded the dictionary values.

It will correctly and uniformly handle both of the cases you were having trouble with:

  • £ (which exceeds the character value range of urlencoding and needs to be encoded into a hexadecimal value in order to transport)
  • & (which, as you say, has meaning in the url as a parameter separator, so that values cannot contain it--so that it has to be encoded as well).

Here's a code example, that shows that the various kinds of example items you mentioned (represented by item1, item2 and item3) now end up correctly urlencoded:

String item1 = "£";
String item2 = "&";
String item3 = "xyz";

Dictionary<string,string> queryDictionary = new Dictionary<string, string>()
{
    {"item1", item1},
    {"item2", item2},
    {"item3", item3}
};

var queryString = new System.Net.Http.FormUrlEncodedContent(queryDictionary)
        .ReadAsStringAsync().Result;

queryString will contain item1=%C2%A3&item2=%26&item3=xyz.

DWright
  • 9,258
  • 4
  • 36
  • 53
  • thanks for your code ..now i was able to achive the correct url encoding using String build as follow:- UriBuilder uri = new System.UriBuilder[ApiURL]); var sb = new System.Text.StringBuilder(); sb.Append("assetType=" + HttpUtility.UrlEncode(AssetType) + "&");uri.Query = sb.ToString(); string xml = client.DownloadString(uri.ToString()); – John John Aug 19 '16 at 08:53
4

Maybe you could try to use an Extension method on the NameValueCollection class. Something like this:

using System.Collections.Specialized;
using System.Text;
using System.Web;

namespace Testing
{
    public static class NameValueCollectionExtension
    {
        public static string ToUtf8UrlEncodedQuery(this NameValueCollection nv)
        {
            StringBuilder sb = new StringBuilder();
            bool firstIteration = true;
            foreach (var key in nv.AllKeys)
            {
                if (!firstIteration)
                    sb.Append("&");
                sb.Append(HttpUtility.UrlEncode(key, Encoding.UTF8))
                    .Append("=")
                    .Append(HttpUtility.UrlEncode(nv[key], Encoding.UTF8));
                firstIteration = false;
            }
            return sb.ToString();
        }
    }
}

Then, in your code you can do this:

url.Query = query.ToUtf8UrlEncodedQuery();

Remember to add a using directive for the namespace where you put the NameValueCollectionExtension class.

user1429080
  • 9,086
  • 4
  • 31
  • 54
  • but not sure if this will work.. because the value for the query["model"] = Model; will be encoded using ISO not using UTF8 ... i did not test your code ,, but i think the value inside the extension method will already be encoded wrongly !.. now i was able to achive the correct url encoding using String build as follow:- UriBuilder uri = new System.UriBuilder[ApiURL]); var sb = new System.Text.StringBuilder(); sb.Append("assetType=" + HttpUtility.UrlEncode(AssetType) + "&");uri.Query = sb.ToString(); string xml = client.DownloadString(uri.ToString()); – John John Aug 19 '16 at 08:52
  • 1
    @johnG The type returned from the `HttpUtility.ParseQueryString` is actually an internal class, `HttpValueCollection`, that derives from `NameValueCollection`. The encoding is done in the `.ToString()` method of `HttpValueCollection`. Accessing them using key and value will give back unencoded strings. So the sample should work as is. – user1429080 Aug 19 '16 at 11:05
  • but when i added check points inside my code .i can see that the problem will happen before reaching "query.ToString()". because i can see that when the code reaches for example "query["model"] = Model;" , the query value will get the following wrong encoding "...&model=%u00a3" ... so this mean that the problem will happen before reaching query.ToString() ... – John John Aug 19 '16 at 12:23
  • @johnG _How_ do you see it? By hovering with the mouse pointer in Visual Studio? If you see the querystring in qs format in Visual Studio, it has already gone through the `.ToString()` method of the type associated with the variable that you are hovering over... The string representation of the variable is generated by a call to `ToString()`. – user1429080 Aug 19 '16 at 12:37
  • i use the Autos Window to check for the query string when the code reaches query["model"] = Model; .. before reaching the query.ToString() .. – John John Aug 19 '16 at 12:41
  • @johnG Yes, and VS shows a querystring. But how do you think VS gets the querystring that it shows? After all, the type of the variable is not a string. So VS calls the `ToString` method on the variable, and _that_ produces an (in your case wrongly) encoded string representation. – user1429080 Aug 19 '16 at 12:46
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/121352/discussion-between-user1429080-and-john-g). – user1429080 Aug 19 '16 at 12:49
2

The problem here isn't UriBuilder.Query, it's UriBuilder.ToString(). Read the documentation here: https://msdn.microsoft.com/en-us/library/system.uribuilder.tostring(v=vs.110).aspx. The property is defined as returning the "display string" of the builder, not a validly encoded string. Uri.ToString() has a similar problem, in that it doesn't perform proper encoding.

Use the following instead: url.Uri.AbsoluteUri, that will always be a properly encoded string. You shouldn't have to do any encoding on the way into the builder (that's part of it's purpose, after all, to properly encode things).

  • 1
    url.Uri.AbsoluteUri will not encode comma in url and this is not working answer! – Tomas Dec 07 '17 at 12:27
  • There's a stack overflow question about URL characters here: https://stackoverflow.com/questions/1547899/which-characters-make-a-url-invalid. Comma's aren't supposed to be encoded, so I'm not sure what your situation of it being a "not working answer" is. –  Dec 19 '17 at 00:59
2

You need to use:

 System.Web.HttpUtility.UrlEncode(key)

Change your code to this:

using (var client = new WebClient())
{
    var query = HttpUtility.ParseQueryString(string.Empty);

    query["model"] = Model;
    //code goes here for other parameters....

    string apiurl = System.Web.Configuration.WebConfigurationManager.AppSettings["ApiURL"];
    var url = new UriBuilder(apiurl);
    url.Query = HttpUtility.UrlEncode(query.ToString());

    string xml = client.DownloadString(url.ToString());
    XmlDocument doc = new XmlDocument();
    //code goes here ....

}
matt-dot-net
  • 4,204
  • 21
  • 24
  • not sure how to use this exactly inside my code? now i was able to achive the correct url encoding using String build as follow:- UriBuilder uri = new System.UriBuilder[ApiURL]); var sb = new System.Text.StringBuilder(); sb.Append("assetType=" + HttpUtility.UrlEncode(AssetType) + "&");uri.Query = sb.ToString(); string xml = client.DownloadString(uri.ToString()); – John John Aug 19 '16 at 08:48
1

If nothing helps, then just manually convert those problematic chars inside values of parameters

& to %26
+ to %2B
? to %3F
T.Todua
  • 53,146
  • 19
  • 236
  • 237