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

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


当前回答

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

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在这个主题上发表了一些文章)。

其他回答

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

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

这里有一种流畅的/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());
}

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

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上可用:

PM>安装包Flurl。Http

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

PM>安装包Flurl