Bagaimana cara membangun string kueri untuk URL dalam C #?

473

Tugas umum saat memanggil sumber daya web dari kode adalah membangun string kueri untuk memasukkan semua parameter yang diperlukan. Meskipun tidak berarti ilmu roket, ada beberapa detail bagus yang perlu Anda perhatikan, menambahkan &parameter if not first, encoding the etc.

Kode untuk melakukannya sangat sederhana, tetapi sedikit membosankan:

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

Ini adalah tugas umum yang diharapkan ada kelas utilitas yang membuatnya lebih elegan dan mudah dibaca. Memindai MSDN, saya gagal menemukannya — yang membawa saya ke pertanyaan berikut:

Apa cara bersih paling elegan yang Anda tahu melakukan hal di atas?

Boas
sumber
26
Agak menyedihkan bahwa bahkan pada titik waktu saat ini, tampaknya tidak ada cara langsung untuk berurusan dengan querystrings. Dan dengan terus terang, yang saya maksud adalah kelas kerangka kerja OOB, non-internal, standar-compliant. Atau mungkin saya melewatkan sesuatu?
Grimace of Despair
5
Anda tidak melewatkan apa pun. Bangunan querystring adalah celah utama dalam kerangka yang saya coba isi dengan Flurl .
Todd Menier
Anda baru saja membuat saya berpikir saya harus membangun satu .. UrlBuilder baru (sudah ada) .AddQuery ("kunci", "nilai"). ToString ()
Demetris Leptos

Jawaban:

293

Jika Anda melihat di bawah kap properti QueryString adalah NameValueCollection. Ketika saya telah melakukan hal-hal serupa, saya biasanya tertarik pada serialisasi DAN deserialising sehingga saran saya adalah untuk membangun NameValueCollection dan kemudian meneruskan ke:

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

Saya membayangkan ada cara super elegan untuk melakukan ini di LINQ juga ...

annakata
sumber
22
HTTP spec (RFC 2616) tidak mengatakan apa-apa tentang string kueri apa yang bisa berisi. RFC 3986 juga tidak mendefinisikan format URI generik. Format pasangan kunci / nilai yang biasa digunakan disebut application/x-www-form-urlencoded, dan sebenarnya ditentukan oleh HTML, untuk tujuan mengirimkan data formulir sebagai bagian dari GETpermintaan. HTML 5 tidak melarang beberapa nilai per kunci dalam format ini, dan pada kenyataannya itu mensyaratkan bahwa browser menghasilkan beberapa nilai per kunci dalam kasus bahwa halaman (salah) berisi beberapa bidang dengan nameatribut yang sama . Lihat goo.gl/uk1Ag
Daniel Cassidy
14
@annakata: Saya tahu komentar saya berusia lebih dari satu tahun (dan jawabannya lebih dari dua tahun!), tetapi NameValueCollection sangat mendukung banyak nilai per kunci, dengan menggunakan metode GetValues ​​(kunci).
Mauricio Scheffer
4
@MauricioScheffer: Tapi NameValueCollection tidak berubah menjadi querystring "dengan benar". Misalnya, jika Anda menetapkan parameter QueryString di WebClient di mana kunci yang sama hadir beberapa kali, itu berubah menjadi "path? Key = value1, value2" bukannya "path? Key = value1 & key = value2", yang merupakan standar umum (standar) ?) pola.
David Pope
8
Mengenai beberapa nilai per kunci, saya percaya bahwa dalam HTML, jika Anda memiliki daftar-kotak multi-pilih dengan beberapa item dipilih dan dikirim, mereka dikirim dalam format beberapa nilai yang disebutkan oleh David.
Sam
10
Anda mungkin ingin menggunakan Uri.EscapeDataString alih-alih HttpUtility.UrlEncode yang lebih portabel. Lihat stackoverflow.com/questions/2573290/…
PEK
687

Anda dapat membuat instance baru yang dapat ditulisi HttpValueCollectiondengan menelepon System.Web.HttpUtility.ParseQueryString(string.Empty), dan kemudian menggunakannya seperti apa pun NameValueCollection. Setelah menambahkan nilai yang Anda inginkan, Anda dapat memanggil ToStringkoleksi untuk mendapatkan string kueri, sebagai berikut:

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

Ini HttpValueCollectioninternal dan jadi Anda tidak bisa langsung membuat instance. Namun, begitu Anda mendapatkan sebuah instance, Anda dapat menggunakannya seperti yang lainnya NameValueCollection. Karena objek aktual yang Anda gunakan adalah HttpValueCollection, memanggil metode ToString akan memanggil metode yang diganti HttpValueCollection, yang memformat koleksi sebagai string kueri yang disandikan URL.

Setelah mencari SO dan web untuk mencari jawaban atas masalah serupa, ini adalah solusi paling sederhana yang bisa saya temukan.

.NET Core

Jika Anda bekerja di .NET Core, Anda bisa menggunakan Microsoft.AspNetCore.WebUtilities.QueryHelperskelas, yang sangat menyederhanakan ini.

https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.webutilities.queryhelpers

Kode sampel:

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));
John Bledsoe
sumber
6
Anda mungkin dapat membuat metode ekstensi yang disebut ToURLQueryString untuk antarmuka IDictionary:public static string ToURLQueryString(this IDictionary dict) { ... }
Roy Tinker
65
Metode ini tidak sesuai standar untuk karakter multibyte. Ini akan menyandikan mereka sebagai% uXXXX bukan% XX% XX. String kueri yang dihasilkan mungkin ditafsirkan secara keliru oleh server web. Ini bahkan didokumentasikan dalam kelas kerangka kerja internal HttpValueCollection yang dikembalikan oleh HttpUtility.ParseQueryString (). Komentar mengatakan bahwa mereka menjaga perilaku ini karena alasan kompatibilitas ke belakang.
alex
25
Perhatikan ada perbedaan penting antara HttpUtilityPraseQueryString ("") dan NameValueCollection () baru - hanya hasil HttpUtility yang akan menggantikan ToString () untuk menghasilkan querystring yang tepat
Frank Schwieterman
7
Bagaimana dengan kasus di mana Anda ingin beberapa contoh nama dalam string kueri? Misalnya, "type = 10 & type = 21".
Finster
7
@Finster Anda dapat menambahkan beberapa instance nama ke string kueri menggunakan Addmetode ini. Yaitu queryString.Add("type", "1"); queryString.Add("type", "2"); Menggunakan Addmetode ini mungkin cara yang lebih baik untuk melakukan ini sepanjang waktu sebenarnya.
jeremysawesome
94

Dengan inspirasi dari komentar Roy Tinker, saya akhirnya menggunakan metode ekstensi sederhana pada kelas Uri yang membuat kode saya ringkas dan bersih:

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

Pemakaian:

Uri url = new Uri("http://localhost/rest/something/browse").
          AddQuery("page", "0").
          AddQuery("pageSize", "200");

Edit - Varian yang memenuhi standar

Seperti yang ditunjukkan beberapa orang, httpValueCollection.ToString()menyandikan karakter Unicode dengan cara yang tidak sesuai standar . Ini adalah varian dari metode ekstensi yang sama yang menangani karakter seperti itu dengan menggunakan HttpUtility.UrlEncodemetode alih-alih metode yang sudah tidak HttpUtility.UrlEncodeUnicodedigunakan lagi .

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;
}
Vedran
sumber
3
Sempurna. Ditambahkan ke perpustakaan di rumah saya. :)
Andy
1
Anda juga harus URL menyandikan nilai. queryString.Add (nama, Uri.EscapeDataString (nilai));
Ufuk Hacıoğulları
2
Terima kasih telah meningkatkan jawaban ini. Ini memperbaiki masalah dengan karakter multibyte.
Ufuk Hacıoğulları
9
Catatan, ini tidak berfungsi dengan url relatif karena Anda tidak dapat membuat UriBuilder dari Uri relatif.
Yuriy Faktorovich
1
Saya menambahkan kunci hapus sehingga duplikat tidak dapat ditambahkan. dotnetfiddle.net/hTlyAd
Paul Totzke
29

Saya menjawab pertanyaan serupa beberapa waktu lalu. Pada dasarnya, cara terbaik adalah menggunakan kelas HttpValueCollection, yaitu ASP.NETRequest.QueryString properti sebenarnya, sayangnya itu internal dalam kerangka NET. Anda bisa menggunakan Reflector untuk mengambilnya (dan letakkan di kelas Utils Anda). Dengan cara ini Anda bisa memanipulasi string kueri seperti NameValueCollection, tetapi dengan semua masalah penyandian / penguraian url Anda.

HttpValueCollectionmeluas NameValueCollection, dan memiliki konstruktor yang mengambil string kueri yang disandikan (termasuk ampersand dan tanda tanya), dan menimpaToString() metode untuk nanti membangun kembali string kueri dari koleksi yang mendasarinya.

Contoh:

  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
Igal Tabachnik
sumber
Terima kasih ... saya perhatikan bahwa NameValueCollection yang dikembalikannya memiliki ToString () yang bertindak berbeda tetapi tidak dapat menemukan alasannya.
calebt
httpValueCollection.ToString()sebenarnya panggilan httpValueCollection.ToString(true)sehingga menambahkan truekejelasan tidak diperlukan.
dav_i
5
HttpValueCollection adalah kelas internal karena itu Anda tidak dapat instantiated.
Ozba
29

Inilah cara fasih / lambda-ish sebagai metode ekstensi (menggabungkan konsep di posting sebelumnya) yang mendukung banyak nilai untuk kunci yang sama. Preferensi pribadi saya adalah ekstensi daripada pembungkus untuk kemampuan menemukan oleh anggota tim lainnya untuk hal-hal seperti ini. Perhatikan bahwa ada kontroversi seputar metode pengkodean, banyak posting tentang hal itu di Stack Overflow (satu posting seperti itu ) dan blogger MSDN (seperti yang ini ).

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

sunting: dengan dukungan nol, meskipun Anda mungkin perlu menyesuaikannya untuk situasi khusus Anda

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;
}
Alfred
sumber
1
Ini gagal jika salah satu dari nilai-nilai itu null
Josh Noe
ini salah ia menghasilkan banyak string kueri untuk setiap pasangan nilai kunci
Gayan
@GayanRanasinghe: Apa artinya itu?
Matti Virkkunen
22

Flurl [pengungkapan: Saya penulis] mendukung pembuatan string permintaan melalui objek anonim (antara lain):

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

Lib pendamping Flurl.Http opsional memungkinkan Anda untuk melakukan panggilan HTTP langsung dari rantai panggilan yang fasih yang sama, memperluasnya menjadi klien REST full-blown:

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

Paket lengkap tersedia di NuGet:

PM> Install-Package Flurl.Http

atau hanya pembuat URL yang berdiri sendiri:

PM> Install-Package Flurl

Todd Menier
sumber
20

Inilah entri saya yang terlambat. Saya tidak suka yang lain karena berbagai alasan, jadi saya menulis sendiri.

Versi ini menampilkan:

  • Penggunaan StringBuilder saja. Tidak ada panggilan ToArray () atau metode ekstensi lainnya. Itu tidak terlihat secantik beberapa tanggapan lainnya, tetapi saya menganggap ini sebagai fungsi inti sehingga efisiensi lebih penting daripada memiliki kode "fasih", "satu garis" yang menyembunyikan ketidakefisienan.

  • Menangani beberapa nilai per kunci. (Tidak membutuhkannya sendiri tetapi hanya untuk membungkam Mauricio;)

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

Contoh Penggunaan

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

Keluaran

http://example.com/stuff?x=1&y=2&foo=bar&foo=baz&special%20chars=%3F%20%3D%20%26
DSO
sumber
Saya suka ini tidak menggunakan HttpUtility yang berada di bawah System.Web dan tidak tersedia di mana-mana.
Kugel
+1 karena tidak menggunakan LINQ dan tidak menggunakan HttpUtility. Saya akan membuat sb kosong dan membuang variabel "bool first" dan kemudian di dalam loop cukup sb.Append (sb.Length == 0? "?": "&") Sebelum sb.AppendFormat (). Sekarang jika nvc kosong, metode ini akan mengembalikan string kosong daripada kesepian "?".
Mathew Leger
Jawaban ini menangani parameter tunggal dengan beberapa nilai. mis? id = 1 & id = 3 & id = 2 & id = 9
Matematika
12

Bagaimana dengan membuat metode ekstensi yang memungkinkan Anda untuk menambahkan parameter dengan gaya fasih seperti ini?

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

Inilah kelebihan yang menggunakan 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);
}

Dan inilah kelebihan yang menggunakan 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));
}
LukeH
sumber
: +1: untuk metode ekstensi berbasis String yang sederhana. Beberapa jawaban yang lain mungkin mencakup lebih kasus tepi, tapi ini cukup untuk situasi saya, dan tidak mengharuskan saya untuk membangun NameValueCollection, HttpValueCollection, atau Uripertama. Terima kasih!
Stanley G.
11

Saya perlu memecahkan masalah yang sama untuk perpustakaan kelas portabel (PCL) yang saya kerjakan. Dalam hal ini, saya tidak memiliki akses ke System.Web jadi saya tidak bisa menggunakan ParseQueryString.

Sebaliknya saya menggunakan System.Net.Http.FormUrlEncodedContentseperti ini:

var url = new UriBuilder("http://example.com");

url.Query = new FormUrlEncodedContent(new Dictionary<string,string>()
{
    {"param1", "val1"},
    {"param2", "val2"},
    {"param3", "val3"},
}).ReadAsStringAsync().Result;
Hortman
sumber
Ini adalah teknik yang saya gunakan, dan telah merujuknya pada pertanyaan lain http://stackoverflow.com/a/26744471/2108310 Satu-satunya perbedaan adalah saya menggunakan array pasangan KeyValue ... selain membutuhkan referensi ke Sistem. Net (yang tersedia PCL seperti yang Anda nyatakan) ini adalah IMHO cara termudah untuk melakukannya tanpa menyertakan beberapa paket pihak ketiga, atau mencoba berjalan pincang bersama-sama beberapa kekacauan spageti buatan sendiri.
Rostov
9
    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());
    }
Jay Douglass
sumber
1
Bagus! Tetapi Anda tidak perlu .ToArray()s.
mpen
7

Tambahkan kelas ini ke proyek Anda

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

Dan gunakan seperti ini:

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"
Gian Marco Gherardi
sumber
Sekarang, ini harus menjadi jawaban yang diterima; berfungsi dengan baik untuk array seperti "foo [] = 1, foo [] = 2" serta menjaga urutan parameter yang sangat penting.
Soroush Falahati
6

Penawaran saya:

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

Pemakaian:

var uri = new Uri("http://stackoverflow.com").AddQuery("such", "method")
                                             .AddQuery("wow", "soFluent");

// http://stackoverflow.com?such=method&wow=soFluent
dav_i
sumber
Saya lebih suka pendekatan Anda Sederhana dan Elegan, Namun, HttpUtility membutuhkan System.Web
Ody
5

Belum diuji, tapi saya pikir sesuatu di sepanjang garis ini akan bekerja dengan sangat baik

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();
Nick Allen
sumber
terenkapsulasi dengan baik tetapi format pada "? {0}" agak mahal :)
annakata
mengubah nama kelas menjadi QueryString .. Permintaan sedikit ambigu
Nick Allen
4

Versi berbasis metode ekstensi cepat:

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

Anda bisa menggunakan klausa where untuk memilih parameter mana yang ditambahkan ke string.

Martin Harris
sumber
3

Dengan asumsi bahwa Anda ingin mengurangi dependensi ke majelis lain dan untuk menjaga hal-hal sederhana, Anda dapat melakukannya:

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

Ini bekerja dengan baik dengan loop juga. Penghapusan ampersand terakhir harus keluar dari loop.

Perhatikan bahwa operator concatenation digunakan untuk meningkatkan keterbacaan. Biaya menggunakannya dibandingkan dengan biaya menggunakan StringBuilder sangat minim (saya pikir Jeff Atwood memposting sesuatu pada topik ini).

Thomas Bratt
sumber
3

Menggabungkan jawaban teratas untuk membuat versi objek anonim :

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

Itu menghasilkan ini:

key2 = value2 & key1 = value1

Berikut kodenya:

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();
    }
}
Luis Perez
sumber
2

Sama seperti solusi yang diterima, tetapi ditransfusikan ke sintaks "dot" 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;
}
Tomino
sumber
2

Saya punya metode ekstensi untuk Uri yang:

  • Menerima objek anonim: uri.WithQuery(new { name = "value" })
  • Menerima koleksi string/stringpasangan (mis. Kamus`2 ).
  • Menerima koleksi string/objectpasangan (mis. RouteValueDictionary ).
  • Menerima NameValueCollection s.
  • Mengurutkan nilai permintaan dengan kunci sehingga nilai yang sama menghasilkan URI yang sama.
  • Mendukung banyak nilai per kunci, mempertahankan pesanan aslinya.

Versi yang didokumentasikan dapat ditemukan di sini .

Ekstensi:

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

Parser kueri:

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

Inilah tes-tesnya:

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"));
Şafak Gür
sumber
2

Kelas pembungkus rantai untuk 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();
        }
    }
}

Contoh penggunaan:

QueryStringBuilder parameters = new QueryStringBuilder()
    .Add("view", ViewBag.PageView)
    .Add("page", ViewBag.PageNumber)
    .Add("size", ViewBag.PageSize);
string queryString = parameters.ToString();
kroehre
sumber
1

Saya menambahkan metode berikut ke kelas PageBase saya.

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

Memanggil:

NameValueCollection querystrings = new NameValueCollection();    
querystrings.Add("language", "en");
querystrings.Add("id", "134");
this.Redirect("http://www.mypage.com", querystrings);
Mike Cole
sumber
1

Saya menulis beberapa metode ekstensi yang saya temukan sangat berguna ketika bekerja dengan QueryStrings. Seringkali saya ingin memulai dengan QueryString saat ini dan memodifikasi sebelum menggunakannya. Sesuatu seperti,

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

Untuk lebih banyak dan sumbernya: http://www.charlesrcook.com/archive/2008/07/23/c-extension-methods-for-asp.net-query-string-operations.aspx

Ini dasar, tapi saya suka gayanya.

ccook
sumber
1

Hanya ingin membuang 2 sen saya:

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

Dokumen mengatakan bahwa uri.Queryakan dimulai dengan ?jika tidak kosong dan Anda harus memotongnya jika Anda akan memodifikasinya.

Catatan yang HttpUtility.UrlEncodeditemukan di System.Web.

Pemakaian:

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

Meskipun tidak elegan, saya memilih versi yang lebih sederhana yang tidak menggunakan NameValueCollecitons- hanya pola pembangun melilit 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
}

Per jawaban yang ada, saya memastikan untuk menggunakan HttpUtility.UrlEncodepanggilan. Ini digunakan seperti:

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
Andrew Gray
sumber
1
// 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"));
}

Keluaran:

http://www.google.com/SomePathName/AnotherPathName?A=BC&SomeQueryKey=SomeQueryValue

Kode; Anda semua bisa berterima kasih kepada saya di suatu tempat, entah bagaimana: D

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);
        }
    }
}
Demetris Leptos
sumber
1

Saya pergi dengan solusi yang diusulkan oleh DSO (dijawab pada 2 Agustus '11 di 7:29), solusinya tidak memerlukan menggunakan HttpUtility. Namun, sesuai dengan artikel yang diposting di Dotnetpearls , menggunakan Kamus lebih cepat (dalam kinerja) daripada menggunakan NameValueCollection. Berikut ini adalah solusi DSO yang dimodifikasi untuk menggunakan Kamus sebagai pengganti 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();
    }
Ticus
sumber
1

String kueri dapat ditambahkan ke URL dengan:

  1. membuat objek pengumpulan nilai nama
  2. tambahkan item string kueri dan nilainya ke objek ini
  3. encode objek pengumpulan nilai nama ini ke url kode yang disediakan di tautan di bawah ini

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);
}
pekerjaan
sumber
0

Saya menulis pembantu untuk proyek pisau cukur saya menggunakan beberapa petunjuk dari jawaban lain.

Bisnis ParseQueryString diperlukan karena kami tidak diizinkan untuk mengutak-atik objek QueryString dari permintaan saat ini.

@helper GetQueryStringWithValue(string key, string value) {
    var queryString = System.Web.HttpUtility.ParseQueryString(HttpContext.Current.Request.QueryString.ToString());
    queryString[key] = value;
    @Html.Raw(queryString.ToString())
}

Saya menggunakannya seperti ini:

location.search = '[email protected]("var-name", "var-value")';

Jika Anda ingin mengambil lebih dari satu nilai, cukup ubah parameter ke Kamus dan tambahkan pasangan ke string kueri.

LOAS
sumber
0

Kode di bawah ini diambil dari HttpValueCollectionimplementasi ToString, melalui ILSpy, yang memberi Anda nama = nilai querystring.

Sayangnya HttpValueCollection adalah kelas internal yang hanya akan Anda dapatkan kembali jika Anda gunakan HttpUtility.ParseQueryString(). Saya menghapus semua bagian kondisi tampilan untuk itu, dan menyandikan secara default:

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();
    }
}
Chris S
sumber
0

Ini identik dengan jawaban yang diterima kecuali sedikit lebih padat:

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

Hanya untuk mereka yang membutuhkan jawaban atas versi 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

Dan versi tanpa 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

Dan versi C # tanpa LINQ:

    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
Stefan Steiger
sumber