ChatGPT解决这个技术问题 Extra ChatGPT

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

从代码调用 Web 资源时的一项常见任务是构建查询字符串以包含所有必要的参数。虽然绝对不是火箭科学,但您需要注意一些漂亮的细节,例如,如果不是第一个参数,则附加一个 &,对参数进行编码等。

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

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,我没找到——这让我想到了以下问题:

您所知道的执行上述操作的最优雅的清洁方式是什么?

有点遗憾的是,即使在当前时间点,似乎也没有直接的方法来处理查询字符串。直截了当,我的意思是 OOB、非内部、符合标准的框架类。或者,也许我错过了什么?
你没有错过任何东西。查询字符串构建是我尝试用 Flurl 填补的框架中的一个主要空白。
你只是让我想我应该建立一个.. new UrlBuilder(existing).AddQuery("key", "value").ToString()
这个答案也适用于容易嵌套的对象enter link description here

S
Shaggy

您可以通过调用 System.Web.HttpUtility.ParseQueryString(string.Empty) 创建一个新的 HttpValueCollection 可写实例,然后将其用作任何 NameValueCollection。添加所需值后,您可以在集合上调用 ToString 以获取查询字符串,如下所示:

NameValueCollection queryString = System.Web.HttpUtility.ParseQueryString(string.Empty);

queryString.Add("key1", "value1");
queryString.Add("key2", "value2");

return queryString.ToString(); // Returns "key1=value1&key2=value2", all URL-encoded

HttpValueCollection 是内部的,因此您不能直接构造实例。但是,一旦您获得了一个实例,您就可以像使用任何其他 NameValueCollection 一样使用它。由于您使用的实际对象是 HttpValueCollection,因此调用 ToString 方法将调用 HttpValueCollection 上的覆盖方法,该方法将集合格式化为 URL 编码的查询字符串。

在搜索 SO 和网络以寻找类似问题的答案后,这是我能找到的最简单的解决方案。

.NET 核心

如果您在 .NET Core 中工作,则可以使用 Microsoft.AspNetCore.WebUtilities.QueryHelpers 类,这大大简化了这一过程。

https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.webutilities.queryhelpers

示例代码:

const string url = "https://customer-information.azure-api.net/customers/search/taxnbr";
var param = new Dictionary<string, string>() { { "CIKey", "123456789" } };

var newUrl = new Uri(QueryHelpers.AddQueryString(url, param));

您可以为 IDictionary 接口创建一个名为 ToURLQueryString 的扩展方法:public static string ToURLQueryString(this IDictionary dict) { ... }
对于多字节字符,此方法不是 standard-compliant。它会将它们编码为 %uXXXX 而不是 %XX%XX。 Web 服务器可能会错误地解释生成的查询字符串。这甚至记录在由 HttpUtility.ParseQueryString() 返回的内部框架类 HttpValueCollection 中。评论说,出于向后兼容性的原因,他们保留了这种行为。
请注意 HttpUtilityPraseQueryString("") 和 new NameValueCollection() 之间有一个重要区别——只有 HttpUtility 结果会覆盖 ToString() 以生成正确的查询字符串
如果您希望在查询字符串中有多个名称实例,该怎么办?例如,“类型=10&类型=21”。
@Finster 您可以使用 Add 方法将名称的多个实例添加到查询字符串。即queryString.Add("type", "1"); queryString.Add("type", "2"); 使用Add 方法实际上可能是始终执行此操作的更好方法。
J
Jay Allen

如果您深入了解 QueryString 属性是 NameValueCollection。当我做了类似的事情时,我通常对序列化和反序列化感兴趣,所以我的建议是建立一个 NameValueCollection 然后传递给:

using System.Linq;
using System.Web;
using System.Collections.Specialized;

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

我想在 LINQ 中也有一种超级优雅的方式来做到这一点......


HTTP 规范 (RFC 2616) 没有说明查询字符串可以包含什么。定义通用 URI 格式的 RFC 3986 也没有。常用的键/值对格式称为 application/x-www-form-urlencoded,实际上是由 HTML 定义的,目的是作为 GET 请求的一部分提交表单数据。 HTML 5 不禁止这种格式的每个键的多个值,实际上它要求浏览器在页面(错误地)包含具有相同 name 属性的多个字段的情况下为每个键生成多个值。请参阅goo.gl/uk1Ag
@annakata:我知道我的评论已经有一年多了(答案也有两年多了!),但是 NameValueCollection 通过使用 GetValues(key) 方法非常支持每个键的多个值。
@MauricioScheffer:但是 NameValueCollection 没有“正确”转换为查询字符串。例如,如果您在 WebClient 上设置多次出现相同键的 QueryString 参数,它会变成“path?key=value1,value2”而不是“path?key=value1&key=value2”,这是一个常见的(标准?) 图案。
关于每个键的多个值,我相信在 HTML 中,如果您有一个多选列表框,其中选择并提交了多个项目,它们将以 David 提到的多值格式发送。
您可能希望使用 Uri.EscapeDataString 而不是更便携的 HttpUtility.UrlEncode。请参阅stackoverflow.com/questions/2573290/…
V
Vedran

受 Roy Tinker 评论的启发,我最终在 Uri 类上使用了一个简单的扩展方法,以保持我的代码简洁明了:

using System.Web;

public static class HttpExtensions
{
    public static Uri AddQuery(this Uri uri, string name, string value)
    {
        var httpValueCollection = HttpUtility.ParseQueryString(uri.Query);

        httpValueCollection.Remove(name);
        httpValueCollection.Add(name, value);

        var ub = new UriBuilder(uri);
        ub.Query = httpValueCollection.ToString();

        return ub.Uri;
    }
}

用法:

Uri url = new Uri("http://localhost/rest/something/browse").
          AddQuery("page", "0").
          AddQuery("pageSize", "200");

编辑 - 符合标准的变体

正如一些人指出的那样,httpValueCollection.ToString()non-standards-compliant 的方式对 Unicode 字符进行编码。这是通过调用 HttpUtility.UrlEncode 方法而不是已弃用的 HttpUtility.UrlEncodeUnicode 方法来处理此类字符的同一扩展方法的变体。

using System.Web;

public static Uri AddQuery(this Uri uri, string name, string value)
{
    var httpValueCollection = HttpUtility.ParseQueryString(uri.Query);

    httpValueCollection.Remove(name);
    httpValueCollection.Add(name, value);

    var ub = new UriBuilder(uri);

    // this code block is taken from httpValueCollection.ToString() method
    // and modified so it encodes strings with HttpUtility.UrlEncode
    if (httpValueCollection.Count == 0)
        ub.Query = String.Empty;
    else
    {
        var sb = new StringBuilder();

        for (int i = 0; i < httpValueCollection.Count; i++)
        {
            string text = httpValueCollection.GetKey(i);
            {
                text = HttpUtility.UrlEncode(text);

                string val = (text != null) ? (text + "=") : string.Empty;
                string[] vals = httpValueCollection.GetValues(i);

                if (sb.Length > 0)
                    sb.Append('&');

                if (vals == null || vals.Length == 0)
                    sb.Append(val);
                else
                {
                    if (vals.Length == 1)
                    {
                        sb.Append(val);
                        sb.Append(HttpUtility.UrlEncode(vals[0]));
                    }
                    else
                    {
                        for (int j = 0; j < vals.Length; j++)
                        {
                            if (j > 0)
                                sb.Append('&');

                            sb.Append(val);
                            sb.Append(HttpUtility.UrlEncode(vals[j]));
                        }
                    }
                }
            }
        }

        ub.Query = sb.ToString();
    }

    return ub.Uri;
}

完美的。添加到我的内部图书馆。 :)
您还应该对值进行 URL 编码。 queryString.Add(name, Uri.EscapeDataString(value));
感谢您改进此答案。它解决了多字节字符的问题。
旁注,这不适用于相对 url,因为您无法从相对 Uri 实例化 UriBuilder。
我添加了一个删除键,因此无法添加重复项。 dotnetfiddle.net/hTlyAd
D
Damian

Flurl [披露:我是作者] 支持通过匿名对象(以及其他方式)构建查询字符串:

var url = "http://www.some-api.com".SetQueryParams(new
{
    api_key = ConfigurationManager.AppSettings["SomeApiKey"],
    max_results = 20,
    q = "Don't worry, I'll get encoded!"
});

可选的 Flurl.Http 配套库允许您直接从同一个流畅的调用链进行 HTTP 调用,将其扩展为成熟的 REST 客户端:

T result = await "https://api.mysite.com"
    .AppendPathSegment("person")
    .SetQueryParams(new { ap_key = "my-key" })
    .WithOAuthBearerToken("MyToken")
    .PostJsonAsync(new { first_name = firstName, last_name = lastName })
    .ReceiveJson<T>();

NuGet 上提供了完整的软件包:

PM> Install-Package Flurl.Http

或者只是独立的 URL 构建器:

PM> Install-Package Flurl


据我尝试,这是可以让您有机会在 QueryString 中添加数组的解决方案!
C
Community

我不久前回答了一个similar question。基本上,最好的方法是使用类 HttpValueCollection,它实际上是 ASP.NET 的 Request.QueryString 属性,不幸的是它在 .NET 框架中是内部的。您可以使用 Reflector 来抓取它(并将其放入您的 Utils 类中)。通过这种方式,您可以像 NameValueCollection 一样操作查询字符串,但所有 url 编码/解码问题都会为您处理好。

HttpValueCollection 扩展了 NameValueCollection,并具有一个构造函数,该构造函数采用 编码 查询字符串(包括与号和问号),并覆盖 ToString() 方法以便稍后从底层重建查询字符串收藏。

例子:

  var coll = new HttpValueCollection();

  coll["userId"] = "50";
  coll["paramA"] = "A";
  coll["paramB"] = "B";      

  string query = coll.ToString(true); // true means use urlencode

  Console.WriteLine(query); // prints: userId=50&paramA=A&paramB=B

谢谢...我注意到它返回的 NameValueCollection 有一个 ToString() 行为不同但无法弄清楚原因。
httpValueCollection.ToString() 实际上调用了 httpValueCollection.ToString(true),因此不需要显式添加 true
HttpValueCollection 是一个内部类,因此您无法实例化它。
C
Community

这是一种 fluent/lambda-ish 作为扩展方法(结合以前帖子中的概念)的方式,它支持同一个键的多个值。我个人的偏好是对包装器的扩展,以便其他团队成员能够发现此类内容。请注意,关于编码方法存在争议,Stack Overflow(一个这样的 post)和 MSDN 博主(如 this one)上有很多关于它的帖子。

public static string ToQueryString(this NameValueCollection source)
{
    return String.Join("&", source.AllKeys
        .SelectMany(key => source.GetValues(key)
            .Select(value => String.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(value))))
        .ToArray());
}

编辑:使用 null 支持,尽管您可能需要根据您的特定情况对其进行调整

public static string ToQueryString(this NameValueCollection source, bool removeEmptyEntries)
{
    return source != null ? String.Join("&", source.AllKeys
        .Where(key => !removeEmptyEntries || source.GetValues(key)
            .Where(value => !String.IsNullOrEmpty(value))
            .Any())
        .SelectMany(key => source.GetValues(key)
            .Where(value => !removeEmptyEntries || !String.IsNullOrEmpty(value))
            .Select(value => String.Format("{0}={1}", HttpUtility.UrlEncode(key), value != null ? HttpUtility.UrlEncode(value) : string.Empty)))
        .ToArray())
        : string.Empty;
}

如果任何值为空,则此操作失败
这是错误的,它为每个键值对生成许多查询字符串
@GayanRanasinghe:这甚至意味着什么?
D
Denis Nutiu

好奇没有人提到 AspNet.Core 中的 QueryBuilder。

当您的查询具有重复键(如 ?filter=a&filter=b)时,它会很有帮助

            var qb = new QueryBuilder();
            qb.Add("filter", new string[] {"A", "B"};

然后您只需将 qb 添加到 URI,它会自动转换为字符串。

https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.extensions.querybuilder?view=aspnetcore-5.0


这里有很多答案,在当前使用 .NET Core 进行开发时,需要一段时间才能找到正确的答案。
似乎无法在 dotnet core 3.1 中访问它。未找到命名空间导入并尝试添加 nuget 包 nuget.org/packages/Microsoft.AspNetCore.App.Ref/3.1.10 导致恢复错误 error: NU1213: The package Microsoft.AspNetCore.App.Ref 3.1.10 has a package type DotnetPlatform that is incompatible with this project. error: Package 'Microsoft.AspNetCore.App.Ref' is incompatible with 'all' frameworks in project
我认为该软件包仅在 dotnet 5 中可用。
在 dotnet 3.1(之前也是 iirc)中提供了包 ,但它具有不兼容 netstandard2.0 的严重缺点,因此对我的类库没有用处。
D
DSO

这是我迟到的条目。由于各种原因,我不喜欢其他任何一个,所以我写了自己的。

该版本特点:

仅使用 StringBuilder。没有 ToArray() 调用或其他扩展方法。它看起来不像其他一些响应那么漂亮,但我认为这是一个核心功能,因此效率比拥有隐藏低效率的“流畅”、“单线”代码更重要。

每个键处理多个值。 (我自己不需要它,只是为了让毛里西奥沉默;) public string ToQueryString(NameValueCollection nvc) { StringBuilder sb = new StringBuilder("?");布尔优先=真; foreach (nvc.AllKeys 中的字符串键) { foreach (nvc.GetValues(key) 中的字符串值) { if (!first) { sb.Append("&"); } sb.AppendFormat("{0}={1}", Uri.EscapeDataString(key), Uri.EscapeDataString(value));第一=假; } } 返回 sb.ToString(); }

示例用法

        var queryParams = new NameValueCollection()
        {
            { "x", "1" },
            { "y", "2" },
            { "foo", "bar" },
            { "foo", "baz" },
            { "special chars", "? = &" },
        };

        string url = "http://example.com/stuff" + ToQueryString(queryParams);

        Console.WriteLine(url);

输出

http://example.com/stuff?x=1&y=2&foo=bar&foo=baz&special%20chars=%3F%20%3D%20%26

我喜欢这不使用 System.Web 下的 HttpUtility 并且并非在任何地方都可用。
+1 不使用 linq 和不使用 HttpUtility。我会创建一个空的 sb 并放弃“bool first”变量,然后在循环中简单地在 sb.AppendFormat() 之前添加 sb.Append(sb.Length == 0 ? "?" : "&")。现在,如果 nvc 为空,该方法将返回一个空字符串而不是一个单独的“?”。
这个答案处理具有多个值的单个参数。例如 ?id=1&id=3&id=2&id=9
h
hortman

我需要为我正在研究的可移植类库 (PCL) 解决相同的问题。在这种情况下,我无权访问 System.Web,因此无法使用 ParseQueryString。

相反,我像这样使用 System.Net.Http.FormUrlEncodedContent

var url = new UriBuilder("http://example.com");

url.Query = new FormUrlEncodedContent(new Dictionary<string,string>()
{
    {"param1", "val1"},
    {"param2", "val2"},
    {"param3", "val3"},
}).ReadAsStringAsync().Result;

这是我使用的技术,并在另一个问题 http://stackoverflow.com/a/26744471/2108310 中引用了它唯一的区别是我使用了一组 KeyValue 对......除了需要对 System.Net 的引用(如您所述,它是可用的 PCL)这是恕我直言,最简单的方法是不包括一些第三方包,或者试图把一些自制的意大利面条混在一起。
L
LukeH

如何创建扩展方法,让您可以像这样以流畅的风格添加参数?

string a = "http://www.somedomain.com/somepage.html"
    .AddQueryParam("A", "TheValueOfA")
    .AddQueryParam("B", "TheValueOfB")
    .AddQueryParam("Z", "TheValueOfZ");

string b = new StringBuilder("http://www.somedomain.com/anotherpage.html")
    .AddQueryParam("A", "TheValueOfA")
    .AddQueryParam("B", "TheValueOfB")
    .AddQueryParam("Z", "TheValueOfZ")
    .ToString(); 

下面是使用 string 的重载:

public static string AddQueryParam(
    this string source, string key, string value)
{
    string delim;
    if ((source == null) || !source.Contains("?"))
    {
        delim = "?";
    }
    else if (source.EndsWith("?") || source.EndsWith("&"))
    {
        delim = string.Empty;
    }
    else
    {
        delim = "&";
    }

    return source + delim + HttpUtility.UrlEncode(key)
        + "=" + HttpUtility.UrlEncode(value);
}

下面是使用 StringBuilder 的重载:

public static StringBuilder AddQueryParam(
    this StringBuilder source, string key, string value)
{
    bool hasQuery = false;
    for (int i = 0; i < source.Length; i++)
    {
        if (source[i] == '?')
        {
            hasQuery = true;
            break;
        }
    }

    string delim;
    if (!hasQuery)
    {
        delim = "?";
    }
    else if ((source[source.Length - 1] == '?')
        || (source[source.Length - 1] == '&'))
    {
        delim = string.Empty;
    }
    else
    {
        delim = "&";
    }

    return source.Append(delim).Append(HttpUtility.UrlEncode(key))
        .Append("=").Append(HttpUtility.UrlEncode(value));
}

:+1: 用于简单的基于字符串的扩展方法。其他一些答案可能涵盖更多的边缘情况,但这对我的情况来说已经足够了,并且不需要我先构造 NameValueCollectionHttpValueCollectionUri。谢谢!
J
Jay Douglass
    public static string ToQueryString(this Dictionary<string, string> source)
    {
        return String.Join("&", source.Select(kvp => String.Format("{0}={1}", HttpUtility.UrlEncode(kvp.Key), HttpUtility.UrlEncode(kvp.Value))).ToArray());
    }

    public static string ToQueryString(this NameValueCollection source)
    {
        return String.Join("&", source.Cast<string>().Select(key => String.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(source[key]))).ToArray());
    }

好的!但您不需要 .ToArray()
N
Nick Allen

未经测试,但我认为这些方面的东西会很好地工作

public class QueryString
{
    private Dictionary<string,string> _Params = new Dictionary<string,string>();

    public overide ToString()
    {
        List<string> returnParams = new List<string>();

        foreach (KeyValuePair param in _Params)
        {
            returnParams.Add(String.Format("{0}={1}", param.Key, param.Value));
        }

        // return String.Format("?{0}", String.Join("&", returnParams.ToArray())); 

        // credit annakata
        return "?" + String.Join("&", returnParams.ToArray());
    }

    public void Add(string key, string value)
    {
        _Params.Add(key, HttpUtility.UrlEncode(value));
    }
}

QueryString query = new QueryString();

query.Add("param1", "value1");
query.Add("param2", "value2");

return query.ToString();

封装得很好,但“?{0}”上的格式有点不必要的昂贵:)
将类名更改为QueryString.. 查询有点模棱两可
G
Gian Marco

将此类添加到您的项目中

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

public class QueryStringBuilder
{
    private readonly List<KeyValuePair<string, object>> _list;

    public QueryStringBuilder()
    {
        _list = new List<KeyValuePair<string, object>>();
    }

    public void Add(string name, object value)
    {
        _list.Add(new KeyValuePair<string, object>(name, value));
    }

    public override string ToString()
    {
        return String.Join("&", _list.Select(kvp => String.Concat(Uri.EscapeDataString(kvp.Key), "=", Uri.EscapeDataString(kvp.Value.ToString()))));
    }
}

并像这样使用它:

var actual = new QueryStringBuilder {
    {"foo", 123},
    {"bar", "val31"},
    {"bar", "val32"}
};

actual.Add("a+b", "c+d");

actual.ToString(); // "foo=123&bar=val31&bar=val32&a%2bb=c%2bd"

现在,这应该是公认的答案;非常适用于像“foo[]=1,foo[]=2”这样的数组,并且保持参数的顺序,顺便说一句,这是非常重要的。
d
dav_i

我的提议:

public static Uri AddQuery(this Uri uri, string name, string value)
{
    // this actually returns HttpValueCollection : NameValueCollection
    // which uses unicode compliant encoding on ToString()
    var query = HttpUtility.ParseQueryString(uri.Query);

    query.Add(name, value);

    var uriBuilder = new UriBuilder(uri)
    {
        Query = query.ToString()
    };

    return uriBuilder.Uri;
}

用法:

var uri = new Uri("http://stackoverflow.com").AddQuery("such", "method")
                                             .AddQuery("wow", "soFluent");

// http://stackoverflow.com?such=method&wow=soFluent

我更喜欢你的方法简单优雅,但是,HttpUtility 需要 System.Web
r
redcalx

在 dotnet 核心 QueryHelpers.AddQueryString() 将接受 IDictionary 键值对。为了节省一些内存分配和 CPU 周期,您可以使用 SortedList<,> 而不是 Dictionary<,>,并具有适当的容量和按排序顺序添加的项目...

var queryParams = new SortedList<string,string>(2);
queryParams.Add("abc", "val1");
queryParams.Add("def", "val2");

string requestUri = QueryHelpers.AddQueryString("https://localhost/api", queryParams);

虽然这似乎是一个非常优雅的 .NET Core 解决方案,但存在一个问题,因为它不支持数组。我尝试发送一个 ID 列表。 github.com/aspnet/AspNetCore/issues/7945
L
Luis Perez

结合最热门的答案来创建一个匿名对象版本:

var queryString = HttpUtility2.BuildQueryString(new
{
    key2 = "value2",
    key1 = "value1",
});

这会产生:

键2=值2&键1=值1

这是代码:

public static class HttpUtility2
{
    public static string BuildQueryString<T>(T obj)
    {
        var queryString = HttpUtility.ParseQueryString(string.Empty);

        foreach (var property in TypeDescriptor.GetProperties(typeof(T)).Cast<PropertyDescriptor>())
        {
            var value = (property.GetValue(obj) ?? "").ToString();
            queryString.Add(property.Name, value);
        }

        return queryString.ToString();
    }
}

M
Martin Harris

基于快速扩展方法的版本:

class Program
{
    static void Main(string[] args)
    {
        var parameters = new List<KeyValuePair<string, string>>
                             {
                                 new KeyValuePair<string, string>("A", "AValue"),
                                 new KeyValuePair<string, string>("B", "BValue")
                             };

        string output = "?" + string.Join("&", parameters.ConvertAll(param => param.ToQueryString()).ToArray());
    }
}

public static class KeyValueExtensions
{
    public static string ToQueryString(this KeyValuePair<string, string> obj)
    {
        return obj.Key + "=" + HttpUtility.UrlEncode(obj.Value);
    }
}

您可以使用 where 子句来选择将哪些参数添加到字符串中。


b
b-zurg

这里有很多很好的答案,但对于那些使用现代 C# 的人来说,这可能是一个很好的实用程序类。

public class QueryParamBuilder
{
    private readonly Dictionary<string, string> _fields = new();
    public QueryParamBuilder Add(string key, string value)
    {
        _fields.Add(key, value);
        return this;
    }
    public string Build()
    {
        return $"?{String.Join("&", _fields.Select(pair => $"{HttpUtility.UrlEncode(pair.Key)}={HttpUtility.UrlEncode(pair.Value)}"))}";
    }
    public static QueryParamBuilder New => new();
}

我在这里使用内部 Dictionary,因为字典在内部是可枚举的键值对,这使得迭代它们比 NameValueCollection 容易得多。

那么查询字符串本身就是一个带有连接的简单插值字符串。

此外,我在构造函数中提供了一个静态接口,以使新生成器的构造变得非常容易,并且只允许一个公开的方法 Add 添加新的查询参数值。最后,您使用 Build() 终止链以实际获得最终字符串。

这是它的用法示例

var queryString = QueryParamBuilder.New
     .Add("id", "0123")
     .Add("value2", 1234.ToString())
     .Add("valueWithSpace","value with spa12!@#@!ce")
     .Build();

结果如预期

?id=0123&value2=1234&valueWithSpace=value+with+spa12!%40%23%40!ce

希望你们中的一些人会觉得这很好,很优雅。


T
Thomas Bratt

假设您想减少对其他程序集的依赖并保持简单,您可以执行以下操作:

var sb = new System.Text.StringBuilder();

sb.Append("a=" + HttpUtility.UrlEncode("TheValueOfA") + "&");
sb.Append("b=" + HttpUtility.UrlEncode("TheValueOfB") + "&");
sb.Append("c=" + HttpUtility.UrlEncode("TheValueOfC") + "&");
sb.Append("d=" + HttpUtility.UrlEncode("TheValueOfD") + "&");

sb.Remove(sb.Length-1, 1); // Remove the final '&'

string result = sb.ToString();

这也适用于循环。最终的 & 号删除需要在循环之外进行。

请注意,连接运算符用于提高可读性。与使用 StringBuilder 的成本相比,使用它的成本是最低的(我认为 Jeff Atwood 在此主题上发布了一些内容)。


Ş
Şafak Gür

我有一个 Uri 的扩展方法:

接受匿名对象:uri.WithQuery(new { name = "value" })

接受字符串/字符串对的集合(例如 Dictionary`2)。

接受字符串/对象对的集合(例如 RouteValueDictionary)。

接受 NameValueCollections。

按键对查询值进行排序,以便相同的值产生相同的 URI。

每个键支持多个值,保留其原始顺序。

可在 here 中找到文档化版本。

扩展:

public static Uri WithQuery(this Uri uri, object values)
{
    if (uri == null)
        throw new ArgumentNullException(nameof(uri));

    if (values != null)
    {
        var query = string.Join(
            "&", from p in ParseQueryValues(values)
                 where !string.IsNullOrWhiteSpace(p.Key)
                 let k = HttpUtility.UrlEncode(p.Key.Trim())
                 let v = HttpUtility.UrlEncode(p.Value)
                 orderby k
                 select string.IsNullOrEmpty(v) ? k : $"{k}={v}");

        if (query.Length != 0 || uri.Query.Length != 0)
            uri = new UriBuilder(uri) { Query = query }.Uri;
    }

    return uri;
}

查询解析器:

private static IEnumerable<KeyValuePair<string, string>> ParseQueryValues(object values)
{
    // Check if a name/value collection.
    var nvc = values as NameValueCollection;
    if (nvc != null)
        return from key in nvc.AllKeys
               from val in nvc.GetValues(key)
               select new KeyValuePair<string, string>(key, val);

    // Check if a string/string dictionary.
    var ssd = values as IEnumerable<KeyValuePair<string, string>>;
    if (ssd != null)
        return ssd;

    // Check if a string/object dictionary.
    var sod = values as IEnumerable<KeyValuePair<string, object>>;
    if (sod == null)
    {
        // Check if a non-generic dictionary.
        var ngd = values as IDictionary;
        if (ngd != null)
            sod = ngd.Cast<dynamic>().ToDictionary<dynamic, string, object>(
                p => p.Key.ToString(), p => p.Value as object);

        // Convert object properties to dictionary.
        if (sod == null)
            sod = new RouteValueDictionary(values);
    }

    // Normalize and return the values.
    return from pair in sod
           from val in pair.Value as IEnumerable<string>
            ?? new[] { pair.Value?.ToString() }
           select new KeyValuePair<string, string>(pair.Key, val);
}

以下是测试:

var uri = new Uri("https://stackoverflow.com/yo?oldKey=oldValue");

// Test with a string/string dictionary.
var q = uri.WithQuery(new Dictionary<string, string>
{
    ["k1"] = string.Empty,
    ["k2"] = null,
    ["k3"] = "v3"
});

Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?k1&k2&k3=v3"));

// Test with a string/object dictionary.
q = uri.WithQuery(new Dictionary<string, object>
{
    ["k1"] = "v1",
    ["k2"] = new[] { "v2a", "v2b" },
    ["k3"] = null
});

Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?k1=v1&k2=v2a&k2=v2b&k3"));

// Test with a name/value collection.
var nvc = new NameValueCollection()
{
    ["k1"] = string.Empty,
    ["k2"] = "v2a"
};

nvc.Add("k2", "v2b");

q = uri.WithQuery(nvc);
Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?k1&k2=v2a&k2=v2b"));

// Test with any dictionary.
q = uri.WithQuery(new Dictionary<int, HashSet<string>>
{
    [1] = new HashSet<string> { "v1" },
    [2] = new HashSet<string> { "v2a", "v2b" },
    [3] = null
});

Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?1=v1&2=v2a&2=v2b&3"));

// Test with an anonymous object.
q = uri.WithQuery(new
{
    k1 = "v1",
    k2 = new[] { "v2a", "v2b" },
    k3 = new List<string> { "v3" },
    k4 = true,
    k5 = null as Queue<string>
});

Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?k1=v1&k2=v2a&k2=v2b&k3=v3&k4=True&k5"));

// Keep existing query using a name/value collection.
nvc = HttpUtility.ParseQueryString(uri.Query);
nvc.Add("newKey", "newValue");

q = uri.WithQuery(nvc);
Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?newKey=newValue&oldKey=oldValue"));

// Merge two query objects using the RouteValueDictionary.
var an1 = new { k1 = "v1" };
var an2 = new { k2 = "v2" };

q = uri.WithQuery(
    new RouteValueDictionary(an1).Concat(
        new RouteValueDictionary(an2)));

Debug.Assert(q == new Uri(
    "https://stackoverflow.com/yo?k1=v1&k2=v2"));

S
Stephen Kennedy

HttpValueCollection 的可链接包装类:

namespace System.Web.Mvc {
    public class QueryStringBuilder {
        private NameValueCollection collection;
        public QueryStringBuilder() {
            collection = System.Web.HttpUtility.ParseQueryString(string.Empty);
        }
        public QueryStringBuilder Add(string key, string value) {
            collection.Add(key, value);
            return this;
        }
        public QueryStringBuilder Remove(string key) {
            collection.Remove(key);
            return this;
        }
        public string this[string key] {
            get { return collection[key]; }
            set { collection[key] = value; }
        }
        public string ToString() {
            return collection.ToString();
        }
    }
}

示例用法:

QueryStringBuilder parameters = new QueryStringBuilder()
    .Add("view", ViewBag.PageView)
    .Add("page", ViewBag.PageNumber)
    .Add("size", ViewBag.PageSize);
string queryString = parameters.ToString();

T
Tomino

与公认的解决方案相同,但转换为“点”LINQ 语法...

private string ToQueryString(NameValueCollection nvc)
{
    if (nvc == null) return String.Empty;
    var queryParams = 
          string.Join("&", nvc.AllKeys.Select(key => 
              string.Join("&", nvc.GetValues(key).Select(v => string.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(v))))));
    return "?" + queryParams;
}

A
Andrew Gray

虽然不优雅,但我选择了一个不使用 NameValueCollecitons 的更简单的版本 - 只是一个围绕 StringBuilder 的构建器模式。

public class UrlBuilder
{
    #region Variables / Properties

    private readonly StringBuilder _builder;

    #endregion Variables / Properties

    #region Constructor

    public UrlBuilder(string urlBase)
    {
        _builder = new StringBuilder(urlBase);
    }

    #endregion Constructor

    #region Methods

    public UrlBuilder AppendParameter(string paramName, string value)
    {
        if (_builder.ToString().Contains("?"))
            _builder.Append("&");
        else
            _builder.Append("?");

        _builder.Append(HttpUtility.UrlEncode(paramName));
        _builder.Append("=");
        _builder.Append(HttpUtility.UrlEncode(value));

        return this;
    }

    public override string ToString()
    {
        return _builder.ToString();
    }

    #endregion Methods
}

根据现有答案,我确保使用 HttpUtility.UrlEncode 调用。它是这样使用的:

string url = new UrlBuilder("http://www.somedomain.com/")
             .AppendParameter("a", "true")
             .AppendParameter("b", "muffin")
             .AppendParameter("c", "muffin button")
             .ToString();
// Result: http://www.somedomain.com?a=true&b=muffin&c=muffin%20button

J
Jon

可以通过以下方式将查询字符串添加到 URL:

创建名称值集合对象将查询字符串项及其值添加到此对象将此名称值集合对象编码到 url 代码在以下链接中提供

https://blog.codingnovice.com/blog

public ActionResult Create()
{
    //declaring name value collection object
    NameValueCollection collection = new NameValueCollection();

    //adding new value to the name value collection object
    collection.Add("Id1", "wwe323");
    collection.Add("Id2", "454w");
    collection.Add("Id3", "tyt5656");
    collection.Add("Id4", "343wdsd");

    //generating query string
    string url = GenerateQueryString(collection);

    return View();
}

private string GenerateQueryString(NameValueCollection collection)
{
    var querystring = (
        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("&", querystring);
}

M
Mike Cole

我在我的 PageBase 类中添加了以下方法。

protected void Redirect(string url)
    {
        Response.Redirect(url);
    }
protected void Redirect(string url, NameValueCollection querystrings)
    {
        StringBuilder redirectUrl = new StringBuilder(url);

        if (querystrings != null)
        {
            for (int index = 0; index < querystrings.Count; index++)
            {
                if (index == 0)
                {
                    redirectUrl.Append("?");
                }

                redirectUrl.Append(querystrings.Keys[index]);
                redirectUrl.Append("=");
                redirectUrl.Append(HttpUtility.UrlEncode(querystrings[index]));

                if (index < querystrings.Count - 1)
                {
                    redirectUrl.Append("&");
                }
            }
        }

        this.Redirect(redirectUrl.ToString());
    }

致电:

NameValueCollection querystrings = new NameValueCollection();    
querystrings.Add("language", "en");
querystrings.Add("id", "134");
this.Redirect("http://www.mypage.com", querystrings);

c
ccook

我编写了一些扩展方法,我发现这些方法在使用 QueryStrings 时非常有用。通常我想从当前的 QueryString 开始并在使用它之前进行修改。就像是,

var res = Request.QueryString.Duplicate()
  .ChangeField("field1", "somevalue")
  .ChangeField("field2", "only if following is true", true)
  .ChangeField("id", id, id>0)
  .WriteLocalPathWithQuery(Request.Url)); //Uses context to write the path

更多信息和来源:http://www.charlesrcook.com/archive/2008/07/23/c-extension-methods-for-asp.net-query-string-operations.aspx

这是基本的,但我喜欢这种风格。


m
mpen

只是想投入我的 2 美分:

public static class HttpClientExt
{
    public static Uri AddQueryParams(this Uri uri, string query)
    {
        var ub = new UriBuilder(uri);
        ub.Query = string.IsNullOrEmpty(uri.Query) ? query : string.Join("&", uri.Query.Substring(1), query);
        return ub.Uri;
    }

    public static Uri AddQueryParams(this Uri uri, IEnumerable<string> query)
    {
        return uri.AddQueryParams(string.Join("&", query));
    } 

    public static Uri AddQueryParams(this Uri uri, string key, string value)
    {
        return uri.AddQueryParams(string.Join("=", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(value)));
    }

    public static Uri AddQueryParams(this Uri uri, params KeyValuePair<string,string>[] kvps)
    {
        return uri.AddQueryParams(kvps.Select(kvp => string.Join("=", HttpUtility.UrlEncode(kvp.Key), HttpUtility.UrlEncode(kvp.Value))));
    }

    public static Uri AddQueryParams(this Uri uri, IDictionary<string, string> kvps)
    {
        return uri.AddQueryParams(kvps.Select(kvp => string.Join("=", HttpUtility.UrlEncode(kvp.Key), HttpUtility.UrlEncode(kvp.Value))));
    }

    public static Uri AddQueryParams(this Uri uri, NameValueCollection nvc)
    {
        return uri.AddQueryParams(nvc.AllKeys.SelectMany(nvc.GetValues, (key, value) => string.Join("=", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(value))));
    }
}

The docs 表示如果 uri.Query 不为空,则它会以 ? 开头,如果要修改它,则应将其剪掉。

请注意,HttpUtility.UrlEncode 位于 System.Web 中。

用法:

var uri = new Uri("https://api.del.icio.us/v1/posts/suggest").AddQueryParam("url","http://stackoverflow.com")

D
Demetris Leptos
// USAGE
[TestMethod]
public void TestUrlBuilder()
{
    Console.WriteLine(
        new UrlBuilder("http://www.google.com?A=B")
            .AddPath("SomePathName")
            .AddPath("AnotherPathName")
            .SetQuery("SomeQueryKey", "SomeQueryValue")
            .AlterQuery("A", x => x + "C"));
}

输出:

http://www.google.com/SomePathName/AnotherPathName?A=BC&SomeQueryKey=SomeQueryValue

编码;你们都可以在某个地方感谢我,不知何故:D

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

// By Demetris Leptos
namespace TheOperator.Foundation.Web
{
    public class UrlBuilder
    {
        public string Scheme { get; set; }

        public string Host { get; set; }

        public int? Port { get; set; }

        public List<string> Paths { get; set; }

        public SortedDictionary<string, string> QueryPairs { get; set; }

        public UrlBuilder(string url)
        {
            this.Paths = new List<string>();
            this.QueryPairs = new SortedDictionary<string, string>();

            string path = null;
            string query = null;
            Uri relativeUri = null;
            if (!Uri.TryCreate(url, UriKind.Relative, out relativeUri))
            {
                var uriBuilder = new UriBuilder(url);
                this.Scheme = uriBuilder.Scheme;
                this.Host = uriBuilder.Host;
                this.Port = uriBuilder.Port;
                path = uriBuilder.Path;
                query = uriBuilder.Query;
            }
            else
            {
                var queryIndex = url.IndexOf('?');
                if (queryIndex >= 0)
                {
                    path = url.Substring(0, queryIndex);
                    query = url.Substring(queryIndex + 1);
                }
                else
                {
                    path = url;
                }
            }
            this.Paths.AddRange(path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries));
            if (query != null)
            {
                var queryKeyValuePairs = HttpUtility.ParseQueryString(query);
                foreach (var queryKey in queryKeyValuePairs.AllKeys)
                {
                    this.QueryPairs[queryKey] = queryKeyValuePairs[queryKey];
                }
            }
        }

        public UrlBuilder AddPath(string value)
        {
            this.Paths.Add(value);
            return this;
        }

        public UrlBuilder SetQuery(string key, string value)
        {
            this.QueryPairs[key] = value;
            return this;
        }

        public UrlBuilder RemoveQuery(string key)
        {
            this.QueryPairs.Remove(key);
            return this;
        }

        public UrlBuilder AlterQuery(string key, Func<string, string> alterMethod, bool removeOnNull = false)
        {
            string value;
            this.QueryPairs.TryGetValue(key, out value);
            value = alterMethod(value);
            if (removeOnNull && value == null)
            {
                return this.RemoveQuery(key);
            }
            else
            {
                return this.SetQuery(key, value);
            }
        }

        public override string ToString()
        {
            var path = !string.IsNullOrWhiteSpace(this.Host)
                ? string.Join("/", this.Host, string.Join("/", this.Paths))
                : string.Join("/", this.Paths);
            var query = string.Join("&", this.QueryPairs.Select(x => string.Concat(x.Key, "=", HttpUtility.UrlEncode(x.Value))));
            return string.Concat(
                !string.IsNullOrWhiteSpace(this.Scheme) ? string.Concat(this.Scheme, "://") : null,
                path,
                !string.IsNullOrWhiteSpace(query) ? string.Concat("?", query) : null);
        }
    }
}

T
Ticus

我采用了 DSO 提出的解决方案(2011 年 8 月 2 日 7:29 回答),他的解决方案不需要使用 HttpUtility。但是,根据 Dotnetpearls 中发布的一篇文章,使用 Dictionary 比使用 NameValueCollection 更快(在性能上)。这是修改为使用 Dictionary 代替 NameValueCollection 的 DSO 解决方案。

    public static Dictionary<string, string> QueryParametersDictionary()
    {
        var dictionary = new Dictionary<string, string>();
        dictionary.Add("name", "John Doe");
        dictionary.Add("address.city", "Seattle");
        dictionary.Add("address.state_code", "WA");
        dictionary.Add("api_key", "5352345263456345635");

        return dictionary;
    }

    public static string ToQueryString(Dictionary<string, string> nvc)
    {
        StringBuilder sb = new StringBuilder();

        bool first = true;

        foreach (KeyValuePair<string, string> pair in nvc)
        {
                if (!first)
                {
                    sb.Append("&");
                }

                sb.AppendFormat("{0}={1}", Uri.EscapeDataString(pair.Key), Uri.EscapeDataString(pair.Value));

                first = false;
        }

        return sb.ToString();
    }

L
LOAS

我使用其他答案中的一些提示为我的剃须刀项目编写了一个助手。

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")';

如果您希望它采用多个值,只需将参数更改为 Dictionary 并将对添加到查询字符串中。