How to build a query string for a URL in C#?

前端 未结 30 2554
借酒劲吻你
借酒劲吻你 2020-11-22 01:55

A common task when calling web resources from a code is building a query string to including all the necessary parameters. While by all means no rocket science, there are so

30条回答
  •  無奈伤痛
    2020-11-22 02:40

    I have an extension method for Uri that:

    • Accepts anonymous objects: uri.WithQuery(new { name = "value" })
    • Accepts collections of string/string pairs (e.g. Dictionary`2).
    • Accepts collections of string/object pairs (e.g. RouteValueDictionary).
    • Accepts NameValueCollections.
    • Sorts the query values by key so the same values produce equal URIs.
    • Supports multiple values per key, preserving their original order.

    The documented version can be found here.

    The extension:

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

    The query parser:

    private static IEnumerable> 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(key, val);
    
        // Check if a string/string dictionary.
        var ssd = values as IEnumerable>;
        if (ssd != null)
            return ssd;
    
        // Check if a string/object dictionary.
        var sod = values as IEnumerable>;
        if (sod == null)
        {
            // Check if a non-generic dictionary.
            var ngd = values as IDictionary;
            if (ngd != null)
                sod = ngd.Cast().ToDictionary(
                    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
                ?? new[] { pair.Value?.ToString() }
               select new KeyValuePair(pair.Key, val);
    }
    

    Here are the tests:

    var uri = new Uri("https://stackoverflow.com/yo?oldKey=oldValue");
    
    // Test with a string/string dictionary.
    var q = uri.WithQuery(new Dictionary
    {
        ["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
    {
        ["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>
    {
        [1] = new HashSet { "v1" },
        [2] = new HashSet { "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 { "v3" },
        k4 = true,
        k5 = null as Queue
    });
    
    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"));
    

提交回复
热议问题