如何在C#中构建URL的查询字符串?

602

在代码中从网络资源获取数据时,常见的任务是构建查询字符串以包含所有必要的参数。虽然这并不是什么高深技术,但您需要注意一些巧妙的细节,例如:如果不是第一个参数,则附加&,对参数进行编码等。

执行此操作的代码非常简单,但有点繁琐:

StringBuilder SB = new StringBuilder();
if (NeedsToAddParameter A) 
{ 
  SB.Append("A="); SB.Append(HttpUtility.UrlEncode("TheValueOfA")); 
}

if (NeedsToAddParameter B) 
{
  if (SB.Length>0) SB.Append("&"); 
  SB.Append("B="); SB.Append(HttpUtility.UrlEncode("TheValueOfB")); }
}

这是一个非常普遍的任务,人们期望存在一个实用程序类使其更加优雅和易读。浏览 MSDN,我未能找到一个—这就引出了以下问题:

你知道最优雅且清晰的方法是什么吗?


32
目前看来,处理查询字符串似乎没有一种简单明了的方式,这有点令人沮丧。所谓简单明了,是指使用一个开箱即用、非内部实现、符合标准的框架类。或者我可能遗漏了某些东西? - Grimace of Despair
6
你没有错过什么。构建查询字符串是这个框架中的一个主要缺陷,我已经尝试用Flurl来填补这个空白。 - Todd Menier
2
我个人使用的技术是我在这个问题中引用的技术:https://dev59.com/YmQn5IYBdhLWcg3wGT8X#26744471 - Rostov
你让我想到了我应该构建一个.. new UrlBuilder(existing).AddQuery("key", "value").ToString() - Demetris Leptos
这个答案同样适用于嵌套对象 链接描述 - giorgi02
41个回答

0

我使用其他答案的一些提示为我的剃刀项目编写了一个帮助程序。

ParseQueryString业务是必要的,因为我们不允许篡改当前请求的QueryString对象。

@helper GetQueryStringWithValue(string key, string value) {
    var queryString = System.Web.HttpUtility.ParseQueryString(HttpContext.Current.Request.QueryString.ToString());
    queryString[key] = value;
    @Html.Raw(queryString.ToString())
}

我用它像这样:
location.search = '?@Helpers.GetQueryStringWithValue("var-name", "var-value")';

如果您想让它接受多个值,只需将参数更改为字典,并将键值对添加到查询字符串中即可。

0

这是另一种(也许有些冗余 :-])实现的方式。

概念与Vedran在此页面中的答案相同(请看这里)。

但是,这个类更加高效,因为它只在调用ToString时迭代所有键一次。

格式化代码也更简单、更好。

希望这能有所帮助。

public sealed class QueryStringBuilder
{
    public QueryStringBuilder()
    {
        this.inner = HttpUtility.ParseQueryString(string.Empty);
    }

    public QueryStringBuilder(string queryString)
    {
        this.inner = HttpUtility.ParseQueryString(queryString);
    }

    public QueryStringBuilder(string queryString, Encoding encoding)
    {
        this.inner = HttpUtility.ParseQueryString(queryString, encoding);
    }

    private readonly NameValueCollection inner;

    public QueryStringBuilder AddKey(string key, string value)
    {
        this.inner.Add(key, value);
        return this;
    }

    public QueryStringBuilder RemoveKey(string key)
    {
        this.inner.Remove(key);
        return this;
    }

    public QueryStringBuilder Clear()
    {
        this.inner.Clear();
        return this;
    }

    public override String ToString()
    {
        if (this.inner.Count == 0)
            return string.Empty;

        var builder = new StringBuilder();

        for (int i = 0; i < this.inner.Count; i++)
        {
            if (builder.Length > 0)
                builder.Append('&');

            var key = this.inner.GetKey(i);
            var values = this.inner.GetValues(i);

            if (key == null || values == null || values.Length == 0)
                continue;

            for (int j = 0; j < values.Length; j++)
            {
                if (j > 0)
                    builder.Append('&');

                builder.Append(HttpUtility.UrlEncode(key));
                builder.Append('=');
                builder.Append(HttpUtility.UrlEncode(values[j]));
            }
        }

        return builder.ToString();
    }
}

0
以下代码取自于ILSpy中HttpValueCollection实现的ToString方法,它返回一个name=value格式的查询字符串。
不幸的是,HttpValueCollection是一个内部类,只有在使用HttpUtility.ParseQueryString()时才能获得。我将其中所有的ViewState部分移除了,并且它默认进行编码:
public static class HttpExtensions
{
    public static string ToQueryString(this NameValueCollection collection)
    {
        // This is based off the NameValueCollection.ToString() implementation
        int count = collection.Count;
        if (count == 0)
            return string.Empty;

        StringBuilder stringBuilder = new StringBuilder();

        for (int i = 0; i < count; i++)
        {
            string text = collection.GetKey(i);
            text = HttpUtility.UrlEncodeUnicode(text);
            string value = (text != null) ? (text + "=") : string.Empty;
            string[] values = collection.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];
                    text2 = HttpUtility.UrlEncodeUnicode(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];
                        text2 = HttpUtility.UrlEncodeUnicode(text2);
                        stringBuilder.Append(text2);
                    }
                }
            }
        }

        return stringBuilder.ToString();
    }
}

0

仅供需要VB.NET版本的最佳答案的人使用:

Public Function ToQueryString(nvc As System.Collections.Specialized.NameValueCollection) As String
    Dim array As String() = nvc.AllKeys.SelectMany(Function(key As String) nvc.GetValues(key), Function(key As String, value As String) String.Format("{0}={1}", System.Web.HttpUtility.UrlEncode(key), System.Web.HttpUtility.UrlEncode(value))).ToArray()
    Return "?" + String.Join("&", array)
End Function

没有使用LINQ的版本:

Public Function ToQueryString(nvc As System.Collections.Specialized.NameValueCollection) As String
    Dim lsParams As New List(Of String)()

    For Each strKey As String In nvc.AllKeys
        Dim astrValue As String() = nvc.GetValues(strKey)

        For Each strValue As String In astrValue
            lsParams.Add(String.Format("{0}={1}", System.Web.HttpUtility.UrlEncode(strKey), System.Web.HttpUtility.UrlEncode(strValue)))
        Next ' Next strValue
    Next ' strKey
    Dim astrParams As String() = lsParams.ToArray()
    lsParams.Clear()
    lsParams = Nothing

    Return "?" + String.Join("&", astrParams)
End Function ' ToQueryString

而没有使用LINQ的C#版本:

    public static string ToQueryString(System.Collections.Specialized.NameValueCollection nvc)
    {
        List<string> lsParams = new List<string>();

        foreach (string strKey in nvc.AllKeys)
        {
            string[] astrValue = nvc.GetValues(strKey);

            foreach (string strValue in astrValue)
            {
                lsParams.Add(string.Format("{0}={1}", System.Web.HttpUtility.UrlEncode(strKey), System.Web.HttpUtility.UrlEncode(strValue)));
            } // Next strValue

        } // Next strKey

        string[] astrParams =lsParams.ToArray();
        lsParams.Clear();
        lsParams = null;

        return "?" + string.Join("&", astrParams);
    } // End Function ToQueryString

0

这与被接受的答案完全相同,只是稍微更加紧凑:

private string ToQueryString(NameValueCollection nvc)
{
    return "?" + string.Join("&", nvc.AllKeys.Select(k => string.Format("{0}={1}", 
        HttpUtility.UrlEncode(k), 
        HttpUtility.UrlEncode(nvc[k]))));
}

0

适用于NameValueCollection中每个键的多个值。

例如:{ {"k1", "v1"}, {"k1", "v1"} } => ?k1=v1&k1=v1

/// <summary>
/// Get query string for name value collection.
/// </summary>
public static string ToQueryString(this NameValueCollection collection,
    bool prefixQuestionMark = true)
{
    collection.NullArgumentCheck();
    if (collection.Keys.Count == 0)
    {
        return "";
    }
    var buffer = new StringBuilder();
    if (prefixQuestionMark)
    {
        buffer.Append("?");
    }
    var append = false;
    for (int i = 0; i < collection.Keys.Count; i++)
    {
        var key = collection.Keys[i];
        var values = collection.GetValues(key);
        key.NullCheck();
        values.NullCheck();
        foreach (var value in values)
        {
            if (append)
            {
                buffer.Append("&");
            }
            append = true;
            buffer.AppendFormat("{0}={1}", key.UrlEncode(), value.UrlEncode());
        }
    }
    return buffer.ToString();
}

0
另一种方法是创建一个类 NameValueCollection 的扩展,返回完整的 Url:
public static class CustomMethods
{
    public static string ToUrl(this System.Collections.Specialized.NameValueCollection collection)
    {
        if (collection.Count == 0) return "";

        string completeUrl = "?";
        for (int i = 0; i < collection.Count; i++)
        {
            completeUrl += new Page().Server.UrlEncode(collection.GetKey(i)) + "=" + new Page().Server.UrlEncode(collection.Get(i));
            if ((i + 1) < collection.Count) completeUrl += "&";
        }

        return completeUrl;
    }
}

然后,您可以使用您的新方法,例如:

System.Collections.Specialized.NameValueCollection qString = new System.Collections.Specialized.NameValueCollection();
qString.Add("name", "MyName");
qString.Add("email", "myemail@test.com");
qString.ToUrl(); //Result: "?name=MyName&email=myemail%40test.com"

0

真是太神奇了,如果我们想在查询字符串中有多个相同的名称,例如"type=10&type=21",我们也可以使用NameValueCollection。让我举个例子:

string MakeQueryString(IEnumerable<KeyValuePair<string, string>> nameValues)
{
    NameValueCollection queryParameters = 
        HttpUtility.ParseQueryString(string.Empty);
    foreach (KeyValuePair<string, string> name in nameValues)
    {
        queryParameters.Add($"{name.Key}", name.Value);
    }

    return $"?{queryParameters}";
}

可以看到,一个名称的多个实例应该放在一个IEnumerable<KeyValuePair<string, string>>数组中。创建的完整示例如下所示:
List<KeyValuePair<string, string>> nameValues = new ()
{
    new KeyValuePair<string, string>("param1", $"1"),
    new KeyValuePair<string, string>("param2", $"2"),
};

int[] numbers = new[] { 3, 4, 5 };
foreach (int number in numbers) 
{
    nameValues.Add(new KeyValuePair<string, string>("param3", $"{number}"));
}

string queryString = MakeQueryString(PrepareQueryParameters(nameValues));

-2

这里有一个使用非常基本语言特性的实现。它是一个我们需要在Objective C中移植和维护的类的一部分,因此我们选择有更多的代码行数,但对于不太熟悉C#的程序员来说更容易移植和理解。

        /// <summary>
        /// Builds a complete http url with query strings.
        /// </summary>
        /// <param name="pHostname"></param>
        /// <param name="pPort"></param>
        /// <param name="pPage">ex "/index.html" or index.html</param>
        /// <param name="pGetParams">a Dictionary<string,string> collection containing the key value pairs.  Pass null if there are none.</param>
        /// <returns>a string of the form: http://[pHostname]:[pPort/[pPage]?key1=val1&key2=val2...</returns>

  static public string buildURL(string pHostname, int pPort, string pPage, Dictionary<string,string> pGetParams)
        {
            StringBuilder sb = new StringBuilder(200);
            sb.Append("http://");
            sb.Append(pHostname);
            if( pPort != 80 ) {
                sb.Append(pPort);
            }
            // Allows page param to be passed in with or without leading slash.
            if( !pPage.StartsWith("/") ) {
                sb.Append("/");
            }
            sb.Append(pPage);

            if (pGetParams != null && pGetParams.Count > 0)
            {
                sb.Append("?");
                foreach (KeyValuePair<string, string> kvp in pGetParams)
                {
                    sb.Append(kvp.Key);
                    sb.Append("=");
                    sb.Append( System.Web.HttpUtility.UrlEncode(kvp.Value) );
                    sb.Append("&");
                }
                sb.Remove(sb.Length - 1, 1); // Remove the final '&'
            }
            return sb.ToString();
        }

-3
public string UrlQueryStr(object data)
{
    if (data == null)
        return string.Empty;

    object val;
    StringBuilder sb = new StringBuilder();

    foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(data))
    {
        if ((val = prop.GetValue(data)) != null)
        {
            sb.AppendFormat("{0}{1}={2}", sb.Length == 0 ? '?' : '&',
                HttpUtility.UrlEncode(prop.Name), HttpUtility.UrlEncode(val.ToString()));
        }
    }
    return sb.ToString();
}

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