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

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


当前回答

你可以通过调用system . web . httputiity . 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));

其他回答

我有一个扩展方法的Uri:

接受匿名对象:uri。WithQuery(new {name = "value"}) 接受字符串/字符串对的集合(例如Dictionary ' 2)。 接受字符串/对象对的集合(例如RouteValueDictionary)。 接受NameValueCollections。 按键对查询值进行排序,以便相同的值产生相同的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"));

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

其概念与本页的《吠陀经》答案相同(请看这里)。

但是这个类更有效,因为它只迭代所有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();
    }
}

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

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

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