追加值到查询字符串

224

我有一组类似以下链接的URL列表:

  • http://somesite.example/backup/lol.php?id=1&server=4&location=us
  • http://somesite.example/news.php?article=1&lang=en

我使用以下代码成功获取了查询字符串:

myurl = longurl.Split('?');
NameValueCollection qs = HttpUtility.ParseQueryString(myurl [1]);

foreach (string lol in qs)
{
    // results will return
}

但它只返回基于提供的URL的参数,如idserverlocation等。

我需要的是在现有的查询字符串中添加/追加值。

例如,对于以下URL:

http://somesite.example/backup/index.php?action=login&attempts=1

我需要更改查询字符串参数的值:

action=login1

attempts=11

正如您所见,我为每个值追加了“1”。我需要从包含不同查询字符串的字符串中获取一组URL,并在末尾为每个参数添加一个值,并将它们再次添加到列表中。

8个回答

448

你可以使用 HttpUtility.ParseQueryString 方法和一个 UriBuilder,这提供了一种良好的方式来处理查询字符串参数,而不必担心解析、URL编码等问题:

string longurl = "http://somesite.example/news.php?article=1&lang=en";
var uriBuilder = new UriBuilder(longurl);
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
query["action"] = "login1";
query["attempts"] = "11";
uriBuilder.Query = query.ToString();
longurl = uriBuilder.ToString();
// "http://somesite.example:80/news.php?article=1&lang=en&action=login1&attempts=11"

7
从我的例子中可以看出,您可以使用变量名作为参数。这正是它的作用:将2个参数附加到我在此处硬编码的现有URL上,但它们完全可以是动态的。 - Darin Dimitrov
4
@UserControl,不是的,HttpUtility.ParseQueryString 方法返回一个特殊的 NameValueCollection 实现,当你设置一个值时,它已经在后台处理了这个问题。 - Darin Dimitrov
8
很遗憾,这个东西依赖于 System.Web :/ - Pure.Krome
4
值得注意的是,这种方法可能会导致国际化方面的问题,因为在query.ToString()方法中,特殊字符将被转换为它们的Unicode等效形式。 - tezromania
2
HttpUtility.ParseQueryString() 返回 HttpValueCollection,它不是公共类并实现了 NameValueCollection - mihkov
显示剩余10条评论

143

我已经将Darin的答案封装成一个易于重复使用的扩展方法。

public static class UriExtensions
{
    /// <summary>
    /// Adds the specified parameter to the Query String.
    /// </summary>
    /// <param name="url"></param>
    /// <param name="paramName">Name of the parameter to add.</param>
    /// <param name="paramValue">Value for the parameter to add.</param>
    /// <returns>Url with added parameter.</returns>
    public static Uri AddParameter(this Uri url, string paramName, string paramValue)
    {
        var uriBuilder = new UriBuilder(url);
        var query = HttpUtility.ParseQueryString(uriBuilder.Query);
        query[paramName] = paramValue;
        uriBuilder.Query = query.ToString();

        return uriBuilder.Uri;
    }
}

1
如果您需要添加具有相同键的多个参数(即a=1&a=2&a=3),则此方法无法正常工作。 - Erik Philips
@ErikPhilips - 在什么情况下,您会想要在单个调用中使用相同的参数名称? - Bonez024
8
虽然有些老旧,但仍然适用。 - Erik Philips
3
你可以使用 query.Add(paramName, paramValue) 来允许具有相同键的多个参数。 - Andrew Richesson
url = url.AddParameter(paramName, paramValue); 如果您使用Brinkie的代码 - GinCanhViet

61

提供的答案存在相对URL的问题,例如“/some/path/”

这是URI和UriBuilder类的限制,这很难理解,因为我没有看到任何相对URL在查询操作时会有什么问题的原因。

以下是一个解决方法,适用于绝对路径和相对路径,在.NET 4中编写和测试:

(小注:这也适用于.NET 4.5,只需将propInfo.GetValue(values,null)更改为propInfo.GetValue(values))

  public static class UriExtensions{
    /// <summary>
    ///     Adds query string value to an existing url, both absolute and relative URI's are supported.
    /// </summary>
    /// <example>
    /// <code>
    ///     // returns "www.domain.example/test?param1=val1&amp;param2=val2&amp;param3=val3"
    ///     new Uri("www.domain.example/test?param1=val1").ExtendQuery(new Dictionary&lt;string, string&gt; { { "param2", "val2" }, { "param3", "val3" } });
    ///
    ///     // returns "/test?param1=val1&amp;param2=val2&amp;param3=val3"
    ///     new Uri("/test?param1=val1").ExtendQuery(new Dictionary&lt;string, string&gt; { { "param2", "val2" }, { "param3", "val3" } });
    /// </code>
    /// </example>
    /// <param name="uri"></param>
    /// <param name="values"></param>
    /// <returns></returns>
    public static Uri ExtendQuery(this Uri uri, IDictionary<string, string> values) {
      var baseUrl = uri.ToString();
      var queryString = string.Empty;
      if (baseUrl.Contains("?")) {
        var urlSplit = baseUrl.Split('?');
        baseUrl = urlSplit[0];
        queryString = urlSplit.Length > 1 ? urlSplit[1] : string.Empty;
      }

      NameValueCollection queryCollection = HttpUtility.ParseQueryString(queryString);
      foreach (var kvp in values ?? new Dictionary<string, string>()) {
        queryCollection[kvp.Key] = kvp.Value;
      }
      var uriKind = uri.IsAbsoluteUri ? UriKind.Absolute : UriKind.Relative;
      return queryCollection.Count == 0
        ? new Uri(baseUrl, uriKind)
        : new Uri(string.Format("{0}?{1}", baseUrl, queryCollection), uriKind);
    }

    /// <summary>
    ///     Adds query string value to an existing url, both absolute and relative URI's are supported.
    /// </summary>
    /// <example>
    /// <code>
    ///     // returns "www.domain.example/test?param1=val1&amp;param2=val2&amp;param3=val3"
    ///     new Uri("www.domain.example/test?param1=val1").ExtendQuery(new { param2 = "val2", param3 = "val3" });
    ///
    ///     // returns "/test?param1=val1&amp;param2=val2&amp;param3=val3"
    ///     new Uri("/test?param1=val1").ExtendQuery(new { param2 = "val2", param3 = "val3" });
    /// </code>
    /// </example>
    /// <param name="uri"></param>
    /// <param name="values"></param>
    /// <returns></returns>
    public static Uri ExtendQuery(this Uri uri, object values) {
      return ExtendQuery(uri, values.GetType().GetProperties().ToDictionary
      (
          propInfo => propInfo.Name,
          propInfo => { var value = propInfo.GetValue(values, null); return value != null ? value.ToString() : null; }
      ));
    }
  }

以下是一组单元测试,用于测试行为:

  [TestFixture]
  public class UriExtensionsTests {
    [Test]
    public void Add_to_query_string_dictionary_when_url_contains_no_query_string_and_values_is_empty_should_return_url_without_changing_it() {
      Uri url = new Uri("http://www.domain.example/test");
      var values = new Dictionary<string, string>();
      var result = url.ExtendQuery(values);
      Assert.That(result, Is.EqualTo(new Uri("http://www.domain.example/test")));
    }

    [Test]
    public void Add_to_query_string_dictionary_when_url_contains_hash_and_query_string_values_are_empty_should_return_url_without_changing_it() {
      Uri url = new Uri("http://www.domain.example/test#div");
      var values = new Dictionary<string, string>();
      var result = url.ExtendQuery(values);
      Assert.That(result, Is.EqualTo(new Uri("http://www.domain.example/test#div")));
    }

    [Test]
    public void Add_to_query_string_dictionary_when_url_contains_no_query_string_should_add_values() {
      Uri url = new Uri("http://www.domain.example/test");
      var values = new Dictionary<string, string> { { "param1", "val1" }, { "param2", "val2" } };
      var result = url.ExtendQuery(values);
      Assert.That(result, Is.EqualTo(new Uri("http://www.domain.example/test?param1=val1&param2=val2")));
    }

    [Test]
    public void Add_to_query_string_dictionary_when_url_contains_hash_and_no_query_string_should_add_values() {
      Uri url = new Uri("http://www.domain.example/test#div");
      var values = new Dictionary<string, string> { { "param1", "val1" }, { "param2", "val2" } };
      var result = url.ExtendQuery(values);
      Assert.That(result, Is.EqualTo(new Uri("http://www.domain.example/test#div?param1=val1&param2=val2")));
    }

    [Test]
    public void Add_to_query_string_dictionary_when_url_contains_query_string_should_add_values_and_keep_original_query_string() {
      Uri url = new Uri("http://www.domain.example/test?param1=val1");
      var values = new Dictionary<string, string> { { "param2", "val2" } };
      var result = url.ExtendQuery(values);
      Assert.That(result, Is.EqualTo(new Uri("http://www.domain.example/test?param1=val1&param2=val2")));
    }

    [Test]
    public void Add_to_query_string_dictionary_when_url_is_relative_contains_no_query_string_should_add_values() {
      Uri url = new Uri("/test", UriKind.Relative);
      var values = new Dictionary<string, string> { { "param1", "val1" }, { "param2", "val2" } };
      var result = url.ExtendQuery(values);
      Assert.That(result, Is.EqualTo(new Uri("/test?param1=val1&param2=val2", UriKind.Relative)));
    }

    [Test]
    public void Add_to_query_string_dictionary_when_url_is_relative_and_contains_query_string_should_add_values_and_keep_original_query_string() {
      Uri url = new Uri("/test?param1=val1", UriKind.Relative);
      var values = new Dictionary<string, string> { { "param2", "val2" } };
      var result = url.ExtendQuery(values);
      Assert.That(result, Is.EqualTo(new Uri("/test?param1=val1&param2=val2", UriKind.Relative)));
    }

    [Test]
    public void Add_to_query_string_dictionary_when_url_is_relative_and_contains_query_string_with_existing_value_should_add_new_values_and_update_existing_ones() {
      Uri url = new Uri("/test?param1=val1", UriKind.Relative);
      var values = new Dictionary<string, string> { { "param1", "new-value" }, { "param2", "val2" } };
      var result = url.ExtendQuery(values);
      Assert.That(result, Is.EqualTo(new Uri("/test?param1=new-value&param2=val2", UriKind.Relative)));
    }

    [Test]
    public void Add_to_query_string_object_when_url_contains_no_query_string_should_add_values() {
      Uri url = new Uri("http://www.domain.example/test");
      var values = new { param1 = "val1", param2 = "val2" };
      var result = url.ExtendQuery(values);
      Assert.That(result, Is.EqualTo(new Uri("http://www.domain.example/test?param1=val1&param2=val2")));
    }

    [Test]
    public void Add_to_query_string_object_when_url_contains_query_string_should_add_values_and_keep_original_query_string() {
      Uri url = new Uri("http://www.domain.example/test?param1=val1");
      var values = new { param2 = "val2" };
      var result = url.ExtendQuery(values);
      Assert.That(result, Is.EqualTo(new Uri("http://www.domain.example/test?param1=val1&param2=val2")));
    }

    [Test]
    public void Add_to_query_string_object_when_url_is_relative_contains_no_query_string_should_add_values() {
      Uri url = new Uri("/test", UriKind.Relative);
      var values = new { param1 = "val1", param2 = "val2" };
      var result = url.ExtendQuery(values);
      Assert.That(result, Is.EqualTo(new Uri("/test?param1=val1&param2=val2", UriKind.Relative)));
    }

    [Test]
    public void Add_to_query_string_object_when_url_is_relative_and_contains_query_string_should_add_values_and_keep_original_query_string() {
      Uri url = new Uri("/test?param1=val1", UriKind.Relative);
      var values = new { param2 = "val2" };
      var result = url.ExtendQuery(values);
      Assert.That(result, Is.EqualTo(new Uri("/test?param1=val1&param2=val2", UriKind.Relative)));
    }

    [Test]
    public void Add_to_query_string_object_when_url_is_relative_and_contains_query_string_with_existing_value_should_add_new_values_and_update_existing_ones() {
      Uri url = new Uri("/test?param1=val1", UriKind.Relative);
      var values = new { param1 = "new-value", param2 = "val2" };
      var result = url.ExtendQuery(values);
      Assert.That(result, Is.EqualTo(new Uri("/test?param1=new-value&param2=val2", UriKind.Relative)));
    }
  }

不幸的是,对于使用云.NET的ASP.NET 5,此解决方案似乎无法使用HttpUtility。但是,除此之外,这是一个很好的解决方案。请参见https://dev59.com/_l0a5IYBdhLWcg3wva16 - diegosasw
1
"Add_to_query_string_dictionary_when_url_contains_hash_and_no_query_string_should_add_values" 应该测试 URL 变成 http://www.domain.com/test?param1=val1&param2=val2#div。 - gliljas
请仔细检查,您是否最好使用uri.AbsoluteUri而不是uri.ToString(),因为可能会产生令人讨厌的未转义效果。 - Nico
2
补充说明:如果URI不是绝对路径,uri.AbsoluteUri会抛出异常! - Nico

47

请注意,您可以从微软添加Microsoft.AspNetCore.WebUtilities NuGet程序包,然后使用它来将值追加到查询字符串:

QueryHelpers.AddQueryString(longurl, "action", "login1")
QueryHelpers.AddQueryString(longurl, new Dictionary<string, string> { { "action", "login1" }, { "attempts", "11" } });

10
从 ASP.NET Core 3.0 开始,WebUtilities 已经成为 ASP.NET SDK 的一部分,因此不需要使用 NuGet 包。 - user1069816
2
AddQueryString 的问题在于它总是添加参数,如果已经存在相同的键,则不会更新该键对应的值,而是创建重复的键,这是不好的。 - Vencovsky
1
@Vencovsky 但是你可以使用 QueryHelpers.ParseQuery 来检查它是否存在。 - DavidG
1
@Vencovsky 我不同意,为什么要在同一个查询字符串上使用相同参数的多个值进行多次传递呢? - Zimano
3
@Vencovsky: “它不会更新,但会创建重复的键,这是不好的。”相反地:在查询字符串中,重复的键是完全有效的。 - Heinzi

17
以下解决方案适用于ASP.NET 5(vNext),并使用QueryHelpers类构建具有参数的URI。
    public Uri GetUri()
    {
        var location = _config.Get("http://iberia.com");
        Dictionary<string, string> values = GetDictionaryParameters();

        var uri = Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString(location, values);
        return new Uri(uri);
    }

    private Dictionary<string,string> GetDictionaryParameters()
    {
        Dictionary<string, string> values = new Dictionary<string, string>
        {
            { "param1", "value1" },
            { "param2", "value2"},
            { "param3", "value3"}
        };
        return values;
    }
结果URI将为http://iberia.com?param1=value1&param2=value2&param3=value3

1
使用字典作为查询键和值的存储方式唯一的问题是查询字符串可能具有不同值的重复键。我相信对 ASP.NET 站点的请求将其解析为该键的值数组。 - Seth

2

我喜欢Bjorn的答案,但是他提供的解决方案是误导性的,因为该方法更新现有参数,而不是在不存在时添加它。为了使其更安全,我做了以下修改。

public static class UriExtensions
{
    /// <summary>
    /// Adds or Updates the specified parameter to the Query String.
    /// </summary>
    /// <param name="url"></param>
    /// <param name="paramName">Name of the parameter to add.</param>
    /// <param name="paramValue">Value for the parameter to add.</param>
    /// <returns>Url with added parameter.</returns>
    public static Uri AddOrUpdateParameter(this Uri url, string paramName, string paramValue)
    {
        var uriBuilder = new UriBuilder(url);
        var query = HttpUtility.ParseQueryString(uriBuilder.Query);

        if (query.AllKeys.Contains(paramName))
        {
            query[paramName] = paramValue;
        }
        else
        {
            query.Add(paramName, paramValue);
        }
        uriBuilder.Query = query.ToString();

        return uriBuilder.Uri;
    }
}

我只是对代码进行了微小的编辑,没有提供它(是原帖作者提供的)...但这会有什么区别呢? - user3638471
1
if/else并不是必要的,只需在所有情况下执行query[paramName] = paramValue;。如果它存在,则会被覆盖。如果不存在,则将创建该键。 - Richard

2

由于现在(.net 5)微软已将许多(全部)使用字符串而不是Uri的方法标记为过时,这更加令人沮丧。

无论如何,操纵相对Uri的更好方法可能是给它所需的内容:

var requestUri = new Uri("x://x").MakeRelativeUri(
   new UriBuilder("x://x") { Path = path, Query = query }.Uri);

你可以使用其他答案来构建查询字符串。

1

摆脱所有URL查询字符串的编辑困境

在经历了大量努力和Uri类以及其他解决方案的摆弄后,这里是我的字符串扩展方法来解决我的问题。

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

public static class StringExtensions
{
    public static string AddToQueryString(this string url, params object[] keysAndValues)
    {
        return UpdateQueryString(url, q =>
        {
            for (var i = 0; i < keysAndValues.Length; i += 2)
            {
                q.Set(keysAndValues[i].ToString(), keysAndValues[i + 1].ToString());
            }
        });
    }

    public static string RemoveFromQueryString(this string url, params string[] keys)
    {
        return UpdateQueryString(url, q =>
        {
            foreach (var key in keys)
            {
                q.Remove(key);
            }
        });
    }

    public static string UpdateQueryString(string url, Action<NameValueCollection> func)
    {
        var urlWithoutQueryString = url.Contains('?') ? url.Substring(0, url.IndexOf('?')) : url;
        var queryString = url.Contains('?') ? url.Substring(url.IndexOf('?')) : null;
        var query = HttpUtility.ParseQueryString(queryString ?? string.Empty);

        func(query);

        return urlWithoutQueryString + (query.Count > 0 ? "?" : string.Empty) + query;
    }
}

1
考虑到已经存在Uri类来表示URL,我建议不要使用原始的字符串来表示。要么使用该类,要么创建一个全新的抽象类(如果缺少功能)。 - julealgon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接