如何建立一个查询字符串的URL在c# ?

当从代码中调用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,我没有找到一个—这让我想到了以下问题:

你所知道的最优雅干净的方法是什么?

448370 次浏览

未经测试,但我认为沿着这些路线的东西会工作得很好

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();

如果你仔细观察,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中也有一种非常优雅的方式来做到这一点……

编辑-正如评论中指出的那样,这不是正确的方法。

有这样一个类——URI类。提供统一资源标识符(URI)的对象表示,并方便访问URI的各个部分。(微软文档)。

下面的例子创建了一个Uri类的实例,并使用它来创建一个WebRequest实例。

c#示例

Uri siteUri = new Uri("http://www.contoso.com/");

WebRequest wr = WebRequest. create (siteUri);

看一下,这个类上有很多方法。

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

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子句来选择将哪些参数添加到字符串中。

我刚才回答了一个类似的问题。基本上,最好的方法是使用类HttpValueCollection。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

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

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));
}

假设你想减少对其他程序集的依赖并保持简单,你可以这样做:

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的成本相比是最小的(我认为杰夫•阿特伍德发布了一些关于这个主题的内容)。

我将以下方法添加到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);

您可以通过调用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://learn.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));

下面是一个使用非常基本的语言特性的实现。它是我们必须在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();
}
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();
}
    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());
}

这是我最近的记录。由于种种原因,我不喜欢其他的,所以我自己写了一个。

这个版本的特点:

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

  • 每个键处理多个值。(我自己不需要,只是为了让毛里西奥闭嘴;)

    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();
    }
    

Example Usage

        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

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();

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

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,并将对添加到查询字符串中。

这里有一种流畅的/lambda-ish方式作为扩展方法(结合了前面文章中的概念),它支持对同一个键使用多个值。我个人更喜欢扩展,而不是包装,以便其他团队成员能够发现这类内容。请注意,关于编码方法存在争议,Stack Overflow(其中一个是帖子)和MSDN博主(如这一个)上有很多关于它的帖子。

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;
}

我写了一些扩展方法,我发现在使用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

很简单,但我喜欢这种风格。

从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;
}

下面的代码是从ToStringHttpValueCollection实现中截取的,通过ILSpy,它给你一个name=value querystring。

不幸的是,HttpValueCollection是一个内部类,只有在使用HttpUtility.ParseQueryString()时才能返回。我删除了它的所有视图状态部分,它默认编码:

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();
}
}

这与公认的答案完全相同,只是稍微更紧凑:

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

只针对那些需要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

将这个类添加到项目中

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"

只是想说说我的看法

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.UrlEncodeSystem.Web中。

用法:

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

我的提供:

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

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 companion lib允许您在相同的流畅调用链上执行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上可用:

# EYZ0

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

# EYZ0

适用于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();
}

与已接受的解决方案相同,但转换为“点”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;
}

我需要为我正在开发的可移植类库(PCL)解决同样的问题。在这种情况下,我没有访问系统的权限。所以我不能用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;

这是另一种(可能是多余的:-])方法。

概念与本页中的< >强Vedran < / >强答案相同(看一下在这里)。

但是这个类更高效,因为它只迭代所有key一次:当调用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();
}
}

结合顶部的答案,创建一个匿名对象版本:

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

这会产生这样的结果:

key2 = value2& key1 = value1

代码如下:

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();
}
}

虽然不够优雅,但我选择了一个更简单的版本,它不使用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

我有一个扩展方法Uri:

  • 接受匿名对象:uri.WithQuery(new { name = "value" })
  • 接受string/string对的集合(例如字典的2)。
  • 接受string/object对的集合(例如RouteValueDictionary)。
  • 接受# EYZ0s。
  • 按键对查询值进行排序,以便相同的值产生相同的uri。
  • 每个键支持多个值,保持其原始顺序。

文档版本可以在在这里中找到。

扩展:

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

的代码;你们都可以在某个地方,以某种方式感谢我

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);
}
}
}

我使用了DSO提出的解决方案(在8月2日11日7:29回答),他的解决方案不需要使用HttpUtility。然而,根据Dotnetpearls上发表的一篇文章,使用Dictionary比使用NameValueCollection更快(在性能上)。下面是修改后的DSO解决方案,使用Dictionary代替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();
}

在dotnet核心QueryHelpers.AddQueryString()将接受一个IDictionary<string,string>键值对的。为了节省一些内存分配和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);

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

  1. 创建名称值集合对象
  2. 将查询字符串项及其值添加到此对象
  3. 将此名称值集合对象编码为下面链接中提供的代码的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);
}

另一种方法是创建一个类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"

奇怪的是没有人提到来自asp.net . core的QueryBuilder。

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

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

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

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

这里有很多很好的答案,但对于使用现代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

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