将NameValueCollection转换为URL查询?

91

我知道我可以做到这个

var nv = HttpUtility.ParseQueryString(req.RawUrl);

但是有没有办法将它转换回URL呢?

var newUrl = HttpUtility.Something("/page", nv);
12个回答

135

仅仅在NameValueCollection上调用ToString()将会以name1=value1&name2=value2的查询字符串格式返回名称值对。请注意,NameValueCollection类型实际上不支持此操作,并且建议这样做是具有误导性的,但由于实际返回的内部类型所以这里可以工作,如下所述。

感谢@mjwills指出HttpUtility.ParseQueryString方法实际上返回的是一个内部的HttpValueCollection对象而不是一个常规的NameValueCollection尽管文档指定为NameValueCollection)。当使用ToString()时,HttpValueCollection会自动编码查询字符串,因此无需编写循环遍历集合并使用UrlEncode方法的程序。期望的结果已经返回。

有了结果,您就可以将其附加到URL并重定向:

var nameValues = HttpUtility.ParseQueryString(Request.QueryString.ToString());
string url = Request.Url.AbsolutePath + "?" + nameValues.ToString();
Response.Redirect(url);

目前使用HttpValueCollection的唯一方法是使用上面展示的ParseQueryString方法(当然除了反射)。看起来这不会改变,因为连接问题请求使该类变为公共类已经关闭,并标记为“不修复”。

另外,您可以调用nameValues上的AddSetRemove方法,在附加之前修改任何查询字符串项。如果您对此感兴趣请参阅我对另一个问题的回答


4
我不确定 nameValues.ToString() 是否能够正确地进行转义。 - user34537
10
实际上,您可以只使用qs.ToString()。这是因为qs不是NameValueCollection(尽管HttpUtility.ParseQueryString方法签名如此)。它实际上是一个名为System.Web.HttpValueCollection的私有类型(您可以使用qs.GetType()来检查)。NameValueCollection.ToString()不进行url编码,但HttpValueCollection.ToString()会进行编码。因此,您对StringBuilder的使用是完全不必要的。 - mjwills
9
我的早期评论可能是错误的。HttpValueCollection 没有调用 HttpUtility.UrlEncode,而似乎调用了 HttpUtility.UrlEncodeUnicode (http://tinyurl.com/HttpValue)。这是一个问题,因为这两者在处理某些字符(例如 é)时有所不同。UrlEncodeUnicode 的新文档似乎暗示它不应该被使用 - http://msdn.microsoft.com/en-us/library/system.web.httputility.urlencodeunicode(v=vs.110).aspx。 - mjwills
59
NameValueCollection.ToString() 方法返回的是 System.Collections.Specialized.NameValueCollection 字符串(确切地说),而不是“准备格式化为查询字符串的名称值对”。请注意不改变原意,简明扼要。 - Igor Brejc
5
谢谢@IgorBrejc,我已经更新了我的帖子,指出声称“NameValueCollection”返回预期格式是具有误导性的。MSDN文档也是具有误导性的,因为它说“ParseQueryString”返回一个“NameValueCollection”,尽管实际上是“HttpValueCollection”,并且在其上调用“ToString()”应该返回预期格式,正如回答的其余部分所提到的那样。 - Ahmad Mageed
显示剩余4条评论

79
string q = String.Join("&",
             nvc.AllKeys.Select(a => a + "=" + HttpUtility.UrlEncode(nvc[a])));

这个答案是唯一一个能正确处理URL参数编码的。赞! - ConfusedAboutCPP
10
唯一回答有关 NameValueCollections 字面问题的选项,而不是提供的涉及 HttpValueCollection 的场景。 - drzaus
5
如果同一个键有多个值,我认为这种方法无法正常工作。 - Martin Brown
6
@MartinBrown是正确的,它不会。以下是适用于多值名称值集合的方法:uriBuilder.Query += String.Join("&", parameters.AllKeys.SelectMany(key => (parameters.GetValues(key) ?? Enumerable.Empty<string>()).Select(val => String.Concat(key, "=", WebUtility.UrlEncode(val))))); - Alex Marshall
4
你还应该对密钥进行编码: string q = String.Join("&", nvc.AllKeys.Select(a => HttpUtility.UrlEncode(a) + "=" + HttpUtility.UrlEncode(nvc[a]))); - SoftDev
当集合包含相同名称的多个值时,我认为这不起作用。 - tymtam

26

编写一个扩展方法,使用一些循环。我更喜欢这个解决方案,因为它易于阅读(没有Linq),不需要System.Web.HttpUtility,并支持重复的键。

public static string ToQueryString(this NameValueCollection nvc)
{
    if (nvc == null) return string.Empty;

    StringBuilder sb = new StringBuilder();

    foreach (string key in nvc.Keys)
    {
        if (string.IsNullOrWhiteSpace(key)) continue;

        string[] values = nvc.GetValues(key);
        if (values == null) continue;

        foreach (string value in values)
        {
            sb.Append(sb.Length == 0 ? "?" : "&");
            sb.AppendFormat("{0}={1}", Uri.EscapeDataString(key), Uri.EscapeDataString(value));
        }
    }

    return sb.ToString();
}

例子

var queryParams = new NameValueCollection()
{
    { "order_id", "0000" },
    { "item_id", "1111" },
    { "item_id", "2222" },
    { null, "skip entry with null key" },
    { "needs escaping", "special chars ? = &" },
    { "skip entry with null value", null }
};

Console.WriteLine(queryParams.ToQueryString());

输出

?order_id=0000&item_id=1111&item_id=2222&needs%20escaping=special%20chars%20%3F%20%3D%20%26

21

这应该可以不需要太多代码就能工作:

NameValueCollection nameValues = HttpUtility.ParseQueryString(String.Empty);
nameValues.Add(Request.QueryString);
// modify nameValues if desired
var newUrl = "/page?" + nameValues;

这个想法是使用HttpUtility.ParseQueryString生成一个空的HttpValueCollection类型集合。该类是NameValueCollection的子类,被标记为internal,因此您的代码不能轻易创建它的实例。

HttpValueCollection的好处在于ToString方法会自动处理编码。通过利用NameValueCollection.Add(NameValueCollection)方法,可以将现有的查询字符串参数添加到新创建的对象中,而无需先将Request.QueryString集合转换为url编码的字符串,然后再解析回集合。

这种技术也可以作为扩展方法公开:

public static string ToQueryString(this NameValueCollection nameValueCollection)
{
    NameValueCollection httpValueCollection = HttpUtility.ParseQueryString(String.Empty);
    httpValueCollection.Add(nameValueCollection);
    return httpValueCollection.ToString();
}

8

实际上,您应该对键进行编码,而不仅仅是值。

string q = String.Join("&",
nvc.AllKeys.Select(a => $"{HttpUtility.UrlEncode(a)}={HttpUtility.UrlEncode(nvc[a])}"));

5

因为NameValueCollection可以有多个相同键的值,如果您关心查询字符串的格式(因为它将作为逗号分隔的值返回而不是“数组表示法”),您可以考虑以下内容。

示例

var nvc = new NameValueCollection();
nvc.Add("key1", "val1");
nvc.Add("key2", "val2");
nvc.Add("empty", null);
nvc.Add("key2", "val2b");

将其转换为:key1=val1&key2[]=val2&empty&key2[]=val2b而不是key1=val1&key2=val2,val2b&empty

代码

string qs = string.Join("&", 
    // "loop" the keys
    nvc.AllKeys.SelectMany(k => {
        // "loop" the values
        var values = nvc.GetValues(k);
        if(values == null) return new[]{ k };
        return nvc.GetValues(k).Select( (v,i) => 
            // 'gracefully' handle formatting
            // when there's 1 or more values
            string.Format(
                values.Length > 1
                    // pick your array format: k[i]=v or k[]=v, etc
                    ? "{0}[]={1}"
                    : "{0}={1}"
                , k, HttpUtility.UrlEncode(v), i)
        );
    })
);

如果你不是很喜欢Linq...

string qs = nvc.ToQueryString(); // using...

public static class UrlExtensions {
    public static string ToQueryString(this NameValueCollection nvc) {
        return string.Join("&", nvc.GetUrlList());
    }

    public static IEnumerable<string> GetUrlList(this NameValueCollection nvc) {
        foreach(var k in nvc.AllKeys) {
            var values = nvc.GetValues(k);
            if(values == null)  { yield return k; continue; }
            for(int i = 0; i < values.Length; i++) {
                yield return
                // 'gracefully' handle formatting
                // when there's 1 or more values
                string.Format(
                    values.Length > 1
                        // pick your array format: k[i]=v or k[]=v, etc
                        ? "{0}[]={1}"
                        : "{0}={1}"
                    , k, HttpUtility.UrlEncode(values[i]), i);
            }
        }
    }
}

正如评论中已经指出的那样,除了这个答案之外,其他大多数答案都是针对场景进行处理(Request.QueryString是一个HttpValueCollection,而不是一个NameValueCollection),而非字面上的问题。

更新:解决了评论中提到的空值问题。


好主意,但是你的Linq代码没有检查null。你如何保证nvc.GetValues(k)不会返回null? - CodingTT
@tianxu0836 干得好;我假设一个没有值的键将返回一个空字符串,但似乎不是这样。 - drzaus

3
简短的回答是在 NameValueCollection 上使用 .ToString() 并将其与原始 URL 组合起来。
然而,我想指出几点:
您不能在 Request.RawUrl 上使用 HttpUtility.ParseQueryStringParseQueryString() 方法正在寻找这样的值:?var=value&var2=value2
如果您想获取 QueryString 参数的 NameValueCollection,只需使用 Request.QueryString()
var nv = Request.QueryString;

重新构建URL只需使用nv.ToString()。
string url = String.Format("{0}?{1}", Request.Path, nv.ToString());

如果您想解析URL字符串而不是使用Request对象,请使用UriHttpUtility.ParseQueryString方法。

Uri uri = new Uri("<THE URL>");
var nv = HttpUtility.ParseQueryString(uri.Query);
string url = String.Format("{0}?{1}", uri.AbsolutePath, nv.ToString());

5
就像评论中指出的(并在此重复),Request.QueryString实际上不是一个NameValueCollection,而是一个特殊的派生类HttpValueCollection,这就是为什么.ToString可以使用的原因;对于常规的nvc,您需要使用类似于此答案中提供的方法。 - drzaus

2
我通常使用UriBuilder将带有查询字符串的URL转换为有效且正确编码的URL。
var url = "http://my-link.com?foo=bar";

var uriBuilder = new UriBuilder(url);
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
query.Add("yep", "foo&bar");

uriBuilder.Query = query.ToString();
var result = uriBuilder.ToString();

// http://my-link.com:80/?foo=bar&yep=foo%26bar

2
在AspNet Core 2.0中,您可以使用QueryHelpers的AddQueryString方法。

0

正如@Atchitutchuk所建议的那样,您可以在ASP.NET Core中使用QueryHelpers.AddQueryString:

    public string FormatParameters(NameValueCollection parameters)
    {
        var queryString = "";
        foreach (var key in parameters.AllKeys)
        {
            foreach (var value in parameters.GetValues(key))
            {
                queryString = QueryHelpers.AddQueryString(queryString, key, value);
            }
        };

        return queryString.TrimStart('?');
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接