17

I have a NameValueCollection in a usercontrol that is initialized like so:

private NameValueCollection _nameValues = HttpUtility.ParseQueryString(Request.QueryString.ToString());

When I call the ToString() on this it generates a proper querystring which I can use for an updated url.

However, when I copy the NameValueCollection via its constructor like so:

var nameValues = new NameValueCollection(_nameValues);

And then try to form an url:

var newUrl = String.Concat(_rootPath + "?" + nameValues.ToString());

It outputs an url like this:

"http://www.domain.com?System.Collections.Specialized.NameValueCollection"

How can I copy a NameValueCollection so that the ToString() method outputs desired results?

Chris Mantle
  • 6,595
  • 3
  • 34
  • 48
Aage
  • 5,932
  • 2
  • 32
  • 57
  • Possible duplicate of [NameValueCollection to URL Query?](https://stackoverflow.com/questions/3865975/namevaluecollection-to-url-query) – dana Jul 13 '17 at 18:19

4 Answers4

24

The problem is there are two actual types in your code. The fist one is System.Web.HttpValueCollection which has it's ToString method overriden to get the result you expect and the second one is System.Collection.Specialized.NameValueCollection which does not override ToString. What you can do, if you really need to use System.Collection.Specialized.NameValueCollection is to create an extension method.

 public static string ToQueryString(this NameValueCollection collection)
 {
        var array = (from key in collection.AllKeys
                     from value in collection.GetValues(key)
                     select string.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(value))).ToArray();
        return "?" + string.Join("&", array);
    }

and use it:

var newUrl = String.Concat(_rootPath,nameValues.ToQueryString());
Luke
  • 22,826
  • 31
  • 110
  • 193
Bogdan Beda
  • 758
  • 5
  • 12
3

It is not NameValueCollection that provides the string formatting. That functionality is in an internal class System.Web.HttpValueCollection that is returned by HttpUtility.ParseQueryString.

So you will not be able to achieve this behavior by using built in functionality. Your best bet would be to create an extension method that formats the values in a URL format.

Here is the method from HttpValueCollection class - you might be able to use it with some modifications.

// System.Web.HttpValueCollection
internal virtual string ToString(bool urlencoded, IDictionary excludeKeys)
{
    int count = this.Count;
    if (count == 0)
    {
        return string.Empty;
    }
    StringBuilder stringBuilder = new StringBuilder();
    bool flag = excludeKeys != null && excludeKeys["__VIEWSTATE"] != null;
    for (int i = 0; i < count; i++)
    {
        string text = this.GetKey(i);
        if ((!flag || text == null || !text.StartsWith("__VIEWSTATE", StringComparison.Ordinal)) && (excludeKeys == null || text == null || excludeKeys[text] == null))
        {
            if (urlencoded)
            {
                text = HttpValueCollection.UrlEncodeForToString(text);
            }
            string value = (text != null) ? (text + "=") : string.Empty;
            string[] values = this.GetValues(i);
            if (stringBuilder.Length > 0)
            {
                stringBuilder.Append('&');
            }
            if (values == null || values.Length == 0)
            {
                stringBuilder.Append(value);
            }
            else
            {
                if (values.Length == 1)
                {
                    stringBuilder.Append(value);
                    string text2 = values[0];
                    if (urlencoded)
                    {
                        text2 = HttpValueCollection.UrlEncodeForToString(text2);
                    }
                    stringBuilder.Append(text2);
                }
                else
                {
                    for (int j = 0; j < values.Length; j++)
                    {
                        if (j > 0)
                        {
                            stringBuilder.Append('&');
                        }
                        stringBuilder.Append(value);
                        string text2 = values[j];
                        if (urlencoded)
                        {
                            text2 = HttpValueCollection.UrlEncodeForToString(text2);
                        }
                        stringBuilder.Append(text2);
                    }
                }
            }
        }
    }
    return stringBuilder.ToString();
}

internal static string UrlEncodeForToString(string input)
{
    return HttpUtility.UrlEncodeUnicode(input);
}
Knaģis
  • 20,827
  • 7
  • 66
  • 80
0

Calling .ToString() on a name value collection will just give you the namespace it belongs to.

I suspect you want the key and value out of it, Assuming that it's the first in the collection why not just do:

var newUrl = String.Concat(_rootPath + "?" + nameValues.GetKey(0) + nameValues.Get(0));

You can have this as an extension method:

public static string ToString(this NameValueCollection nvc, int idx)
{
     if(nvc == null)
         throw new NullReferenceException();

     string key = nvc[idx];
     if(nvc.HasKeys() && !string.IsNullOrEmpty(key))
     {
         return string.Concat(key, nvc.Get(key)); //maybe want some formatting here
     }

     return string.Empty;
}

Usage:

NameValueCollection nvc = new NameValueCollection();
string foo = nvc.ToString(0); //gets key + value at index 0
DGibbs
  • 14,316
  • 7
  • 44
  • 83
0

There is a better way of working around this.

Instead of:

var nameValues = new NameValueCollection(_nameValues);

Do:

var nameValues = HttpUtility.ParseQueryString(_nameValues.ToString()));

And this should create a new copy of correct type.

Ghasan غسان
  • 5,577
  • 4
  • 33
  • 44