如何build立在C#中的URL的查询string?
从代码调用Web资源时的常见任务是构build一个查询string,以包含所有必要的参数。 虽然通过所有手段没有火箭科学,有一些漂亮的细节,你需要照顾像,附加一个&
如果不是第一个参数,编码参数等。
这样做的代码很简单,但有点乏味:
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,我没有find一个 – 这让我到以下问题:
什么是你知道做上述最优雅的清洁的方式?
如果你看看引擎盖下的QueryString属性是一个NameValueCollection。 当我做了类似的事情时,我通常对序列化和反序列化感兴趣,所以我的build议是build立一个NameValueCollection,然后传递给:
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也…
您可以通过调用System.Web.HttpUtility.ParseQueryString(string.Empty)
创build一个新的HttpValueCollection
的可写实例,然后将其用作任何NameValueCollection
。 一旦添加了所需的值,就可以在集合上调用ToString
来获取查询string,如下所示:
NameValueCollection queryString = System.Web.HttpUtility.ParseQueryString(string.Empty); queryString["key1"] = "value1"; queryString["key2"] = "value2"; return queryString.ToString(); // Returns "key1=value1&key2=value2", all URL-encoded
HttpValueCollection
是内部的,所以你不能直接构造一个实例。 但是,一旦你获得一个实例,你可以像使用其他的NameValueCollection
一样使用它。 由于您正在使用的实际对象是HttpValueCollection
,因此调用ToString方法将调用HttpValueCollection
上的重写方法,该方法将集合的格式设置为URL编码的查询string。
在search完这个网页并回答了类似的问题之后,这是我能find的最简单的解决scheme。
.NET核心
如果您在.NET Core中工作,则可以使用Microsoft.AspNetCore.WebUtilities.QueryHelpers
类,这可以大大简化这一点。
https://docs.microsoft.com/en-us/aspnet/core/api/microsoft.aspnetcore.webutilities.queryhelpers
从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()
以非标准兼容的方式编码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; }
我刚才回答了一个类似的问题 。 基本上,最好的方法是使用ASP.NET的Request.QueryString
属性实际上是类HttpValueCollection
,不幸的是它在.NET框架内部。 你可以使用Reflector来抓取它(并把它放到你的Utils类中)。 这样,您可以像查询NameValueCollection一样操作查询string,但是所有的url编码/解码问题都会照顾到您。
HttpValueCollection
扩展了NameValueCollection
,并且有一个构造函数,它接受一个编码的查询string(包括&符号和问号),并且重写一个ToString()
方法以便稍后从底层集合重build查询string。
例:
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¶mA=A¶mB=B
下面是stream式/ lambda-ish方式作为一个扩展方法(结合以前的文章中的概念),支持同一个键的多个值。 我个人的偏好是对其他团队成员发现这种东西的包装的扩展。 请注意,有关编码方法的争议,大量关于堆栈溢出(一个这样的post )和MSDN博客(如这个 )的post 。
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()); }
编辑:无支持,但你可能需要适应你的特定情况
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; }
Flurl [披露:我是作者]支持通过匿名对象(以及其他方式)构build查询string:
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伴侣库允许您直接从相同的stream利调用链执行HTTP调用,将其扩展到一个完整的REST客户端:
await "https://api.mysite.com" .AppendPathSegment("person") .SetQueryParams(new { ap_key = "my-key" }) .WithOAuthBearerToken("MyToken") .PostJsonAsync(new { first_name = firstName, last_name = lastName });
NuGet提供完整的软件包:
PM> Install-Package Flurl.Http
或者只是独立的URL构build器:
PM> Install-Package Flurl
这是我迟到的入口。 我因为各种原因不喜欢其他人,所以我写了自己的。
此版本的特点:
-
仅使用StringBuilder。 没有ToArray()调用或其他扩展方法。 它看起来并不像其他答案那么漂亮,但我认为这是一个核心function,所以效率比隐藏低效率的“stream畅”,“单线”代码更重要。
-
处理每个键的多个值。 (我自己不需要,只是为了沉默毛里西奥;)
public string ToQueryString(NameValueCollection nvc) { StringBuilder sb = new StringBuilder("?"); bool first = true; foreach (string key in nvc.AllKeys) { foreach (string value in nvc.GetValues(key)) { if (!first) { sb.Append("&"); } sb.AppendFormat("{0}={1}", Uri.EscapeDataString(key), Uri.EscapeDataString(value)); first = false; } } return 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
如何创build扩展方法,让你可以像这样stream畅的风格添加参数?
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)); }
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()); }
我需要为我正在处理的便携式类库(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;
我的奉献:
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
将这个类添加到您的项目
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"
没有经过testing,但我认为沿着这些路线会很好地工作
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();
基于版本的快速扩展方法:
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子句来select将哪些参数添加到string中。
假设你想减less对其他程序集的依赖关系,并保持简单,你可以这样做:
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在这个主题上发表了一些东西)。
[也迟到了]
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();
与接受的解决scheme相同,但转换为“点”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; }
结合顶部答案来创build一个匿名对象版本 :
var queryString = HttpUtility2.BuildQueryString(new { key2 = "value2", key1 = "value1", });
这产生了这个:
键2 =值&键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(); } }
我将下面的方法添加到了我的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);
我写了一些扩展方法,我发现使用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
这是基本的,但我喜欢这种风格。
只是想扔我的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)))); } }
文档说uri.Query
将从一个开始?
如果它不是空的,如果你要修改它,你应该把它关掉。
请注意HttpUtility.UrlEncode
在System.Web
find。
用法:
var uri = new Uri("https://api.del.icio.us/v1/posts/suggest").AddQueryParam("url","http://stackoverflow.com")
我有一个Uri的扩展方法:
- 接受匿名对象:
uri.WithQuery(new { name = "value" })
- 接受
string/string
对的集合(例如Dictionary`2 )。 - 接受
string/object
集合(例如RouteValueDictionary )。 - 接受NameValueCollection s。
- 按键对查询值进行sorting,使相同的值产生相同的URI。
- 支持每个键的多个值,保持原来的顺序。
logging的版本可以在这里find。
扩展名:
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; }
查询parsing器:
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); }
这里是testing:
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"));
// 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
The code; you can all thank me somewhere, somehow 😀
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); } } }
I went with the solution proposed by DSO (answered on Aug 2 '11 at 7:29), his solution does not require using HttpUtility. However, as per an article posted in Dotnetpearls , using a Dictionary is faster (in performance) than using NameValueCollection. Here is DSO's solution modified to use Dictionary in place of NameValueCollection.
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(); }
I wrote a helper for my razor project using some of the hints from other answers.
The ParseQueryString business is necessary because we are not allowed to tamper with the QueryString object of the current request.
@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")';
If you want it to take more than one value, just change the parameters to a Dictionary and add the pairs to the query string.
The code below is taken off the HttpValueCollection
implementation of ToString
, via ILSpy, which gives you a name=value querystring.
Unfortunately HttpValueCollection is an internal class which you only ever get back if you use HttpUtility.ParseQueryString()
. I removed all the viewstate parts to it, and it encodes by default:
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(); } }
This is the identical to the accepted answer except slightly more compact:
private string ToQueryString(NameValueCollection nvc) { return "?" + string.Join("&", nvc.AllKeys.Select(k => string.Format("{0}={1}", HttpUtility.UrlEncode(k), HttpUtility.UrlEncode(nvc[k])))); }
Just for those that need the VB.NET version of the top-answer:
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
And the version without 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
And the C# version without LINQ:
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
Works for multiple values per key in NameValueCollection.
ex: { {"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(); }
This is another ( maybe redundant :-] ) way for do that.
The conceptuals are the same of the Vedran answer in this page (take a look here ).
But this class is more efficient, because it iterate through all Keys only one time: when ToString
is invoked.
The formatting code is also semplified and improved.
Hope that could be helpful.
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(); } }