Jak zbudować ciąg zapytania dla adresu URL w C #?


473

Typowym zadaniem podczas wywoływania zasobów sieciowych z kodu jest budowanie ciągu zapytania zawierającego wszystkie niezbędne parametry. Chociaż z całą pewnością nie ma wiedzy o rakietach, jest kilka fajnych szczegółów, o które trzeba dbać, dodając &pierwszy parametr, jeśli nie pierwszy, kodując parametry itp.

Kod do zrobienia tego jest bardzo prosty, ale nieco żmudny:

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

Jest to tak powszechne zadanie, jakiego można oczekiwać od klasy użytkowej, która czyni ją bardziej elegancką i czytelną. Podczas skanowania MSDN nie udało mi się go znaleźć - co prowadzi mnie do następującego pytania:

Jaki jest najbardziej elegancki i czysty sposób wykonywania powyższych czynności?


26
To trochę smutne, że nawet w obecnym momencie wydaje się, że nie ma prostego sposobu radzenia sobie z pytaniami. Mówiąc wprost, mam na myśli OOB, wewnętrzną, niezgodną ze standardami klasę szkieletową. A może coś mi brakuje?
Grymas rozpaczy

5
Niczego nie brakuje. Budowanie Quistystring to główna luka w strukturze, którą próbowałem wypełnić Flurl .
Todd Menier


Właśnie pomyślałem, że powinienem zbudować jeden .. nowy UrlBuilder (istniejący) .AddQuery („klucz”, „wartość”). ToString ()
Demetris Leptos

Odpowiedzi:


293

Jeśli spojrzysz pod maską, właściwość QueryString to NameValueCollection. Kiedy robiłem podobne rzeczy, zwykle byłem zainteresowany serializacją ORAZ deserializacją, więc moją sugestią jest zbudowanie NameValueCollection w górę, a następnie przejście do:

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

Wyobrażam sobie, że jest też bardzo elegancki sposób na zrobienie tego w LINQ ...


22
Specyfikacja HTTP (RFC 2616) nie mówi nic o tym, jakie ciągi zapytania mogą zawierać. RFC 3986 nie definiuje również ogólnego formatu URI. Format pary klucz / wartość, który jest powszechnie używany, nazywa się application/x-www-form-urlencodedi jest faktycznie zdefiniowany przez HTML, w celu przesłania danych formularza w ramach GETżądania. HTML 5 nie zabrania wielu wartości na klucz w tym formacie i w rzeczywistości wymaga, aby przeglądarka wygenerowała wiele wartości na klucz w przypadku, gdy strona (niepoprawnie) zawiera wiele pól o tym samym nameatrybucie. Zobacz goo.gl/uk1Ag
Daniel Cassidy

14
@annakata: Wiem, że mój komentarz ma ponad rok (a odpowiedź ponad dwa lata!), ale NameValueCollection bardzo obsługuje wiele wartości na klucz, używając metody GetValues ​​(klucz).
Mauricio Scheffer,

4
@MauricioScheffer: Ale NameValueCollection nie przekształca się w kwerendę „poprawnie”. Na przykład, jeśli ustawisz parametr QueryString na WebClient, w którym ten sam klucz jest obecny wiele razy, zamienia się on w „ścieżka? Klucz = wartość1, wartość2” zamiast „ścieżka? Klucz = wartość1 i klucz = wartość2”, co jest powszechne (standard ?) wzór.
David Pope,

8
Jeśli chodzi o wiele wartości na klucz, uważam, że w HTML, jeśli masz pole listy wielokrotnego wyboru z wieloma elementami wybranymi i przesłanymi, są one wysyłane w formacie wielu wartości wymienionym przez Davida.
Sam

10
Możesz użyć Uri.EscapeDataString zamiast HttpUtility.UrlEncode, który jest bardziej przenośny. Zobacz stackoverflow.com/questions/2573290/…
PEK

687

Możesz utworzyć nową instancję do zapisu HttpValueCollection, dzwoniąc System.Web.HttpUtility.ParseQueryString(string.Empty), a następnie użyć jej jako dowolnej NameValueCollection. Po dodaniu żądanych wartości możesz wywołać ToStringkolekcję, aby uzyskać ciąg zapytania, w następujący sposób:

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

HttpValueCollectionJest wewnętrzna i nie można więc bezpośrednio skonstruowania instancji. Jednak po uzyskaniu instancji możesz używać jej tak samo, jak każdej innej NameValueCollection. Ponieważ rzeczywistym obiektem, z którym pracujesz, jest HttpValueCollectionwywołanie metody ToString wywoła metodę przesłoniętą HttpValueCollection, która formatuje kolekcję jako ciąg zapytania zakodowany w adresie URL.

Po przeszukaniu SO i Internetu w poszukiwaniu odpowiedzi na podobny problem jest to najprostsze rozwiązanie, jakie udało mi się znaleźć.

.NET Core

Jeśli pracujesz w .NET Core, możesz użyć Microsoft.AspNetCore.WebUtilities.QueryHelpersklasy, co znacznie to upraszcza.

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

Przykładowy kod:

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

6
Prawdopodobnie możesz utworzyć metodę rozszerzenia o nazwie ToURLQueryString dla interfejsu IDictionary:public static string ToURLQueryString(this IDictionary dict) { ... }
Roy Tinker

65
Ta metoda nie jest zgodna ze standardami dla znaków wielobajtowych. Koduje je jako% uXXXX zamiast% XX% XX. Wynikowe ciągi zapytań mogą być nieprawidłowo interpretowane przez serwery WWW. Jest to nawet udokumentowane w wewnętrznej klasie frameworka HttpValueCollection, która jest zwracana przez HttpUtility.ParseQueryString (). Komentarz mówi, że zachowują to zachowanie z powodów kompatybilności wstecznej.
alex

25
Zauważ, że istnieje ważna różnica między HttpUtilityPraseQueryString („”) a nową NameValueCollection () - tylko wynik HttpUtility zastąpi ToString () w celu uzyskania odpowiedniego zapytania
Frank Schwieterman

7
Co z przypadkami, w których chcesz wielu wystąpień nazwy w ciągu zapytania? Na przykład „typ = 10 i typ = 21”.
Finster

7
@ Finster Możesz dodać wiele wystąpień nazwy do ciągu zapytania za pomocą tej Addmetody. Tj. queryString.Add("type", "1"); queryString.Add("type", "2"); Stosowanie tej Addmetody jest prawdopodobnie lepszym sposobem na robienie tego przez cały czas.
jeremysawesome

94

Inspirując się komentarzem Roya Tinkera, skończyłem na prostej metodzie rozszerzenia klasy Uri, dzięki czemu mój kod jest zwięzły i czysty:

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

Stosowanie:

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

Edycja - wariant zgodny ze standardami

Jak zauważyło kilka osób, httpValueCollection.ToString()koduje znaki Unicode w sposób niezgodny ze standardami . Jest to wariant tej samej metody rozszerzenia, która obsługuje takie znaki, wywołując HttpUtility.UrlEncodemetodę zamiast przestarzałej HttpUtility.UrlEncodeUnicodemetody.

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

3
Doskonały. Dodano do mojej wewnętrznej biblioteki. :)
Andy,

1
Powinieneś także zakodować wartość URL. queryString.Add (nazwa, Uri.EscapeDataString (wartość));
Ufuk Hacıoğulları

2
Dziękujemy za ulepszenie tej odpowiedzi. Naprawiono problem ze znakami wielobajtowymi.
Ufuk Hacıoğulları

9
Na marginesie, to nie działa z względnymi adresami URL, ponieważ nie można utworzyć instancji UriBuilder z względnego Uri.
Jurij Faktorowicz

1
Dodałem klucz usuwania, aby nie można było dodać duplikatu. dotnetfiddle.net/hTlyAd
Paul Totzke

29

Jakiś czas temu odpowiedziałem na podobne pytanie . Zasadniczo najlepszym sposobem byłoby użycie klasy HttpValueCollection, którą Request.QueryStringfaktycznie jest właściwość ASP.NET , niestety jest ona wewnętrzna w środowisku .NET. Możesz użyć Odbłyśnika, aby go złapać (i umieścić w swojej klasie Utils). W ten sposób możesz manipulować ciągiem zapytania jak NameValueCollection, ale przy wszystkich problemach związanych z kodowaniem / dekodowaniem adresów URL.

HttpValueCollectionrozszerza NameValueCollectioni ma konstruktor, który pobiera zakodowany ciąg zapytania (w tym znaki handlowe i znaki zapytania), i zastępuje ToString()metodę późniejszej odbudowy ciągu zapytania z podstawowej kolekcji.

Przykład:

  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

Dziękuję ... zauważyłem, że zwracana przez NameValueCollection funkcja ToString () działa inaczej, ale nie mogła zrozumieć, dlaczego.
calebt

httpValueCollection.ToString()faktycznie wywołuje, httpValueCollection.ToString(true)więc dodanie truejawności nie jest wymagane.
dav_i

5
HttpValueCollection jest klasą wewnętrzną, dlatego nie można jej utworzyć.
ozba

29

Oto płynny / lambda-ish sposób jako metoda rozszerzenia (łączenie pojęć z poprzednich postów), który obsługuje wiele wartości dla tego samego klucza. Osobiście wolę rozszerzenia niż opakowania, aby inni członkowie zespołu mogli odkryć takie rzeczy. Zwróć uwagę, że istnieją metody kontrowersji dotyczące metod kodowania, mnóstwo postów na ten temat na Stack Overflow (jeden taki post ) i blogerów MSDN (takich jak ten ).

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

edycja: z obsługą zerową, chociaż prawdopodobnie będziesz musiał dostosować go do konkretnej sytuacji

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

1
Nie udaje się to, jeśli którakolwiek z wartości jest pusta
Josh Noe

to źle, że generuje wiele ciągów zapytań dla każdej pary wartości klucza
Gayan

@GayanRanasinghe: Co to w ogóle oznacza?
Matti Virkkunen,

22

Flurl [ujawnienie: jestem autorem] obsługuje budowanie ciągów zapytań za pomocą anonimowych obiektów (między innymi):

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

Opcjonalna biblioteka towarzysząca Flurl.Http umożliwia wykonywanie połączeń HTTP bezpośrednio z tego samego płynnego łańcucha połączeń, rozszerzając go na pełnoprawnego klienta 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>();

Pełny pakiet jest dostępny na NuGet:

PM> Install-Package Flurl.Http

lub po prostu samodzielny program do tworzenia adresów URL:

PM> Install-Package Flurl


20

Oto mój późny wpis. Nie lubiłem żadnego z innych z różnych powodów, więc napisałem własny.

Ta wersja zawiera:

  • Używanie tylko StringBuilder. Brak wywołań ToArray () lub innych metod rozszerzenia. Nie wygląda tak ładnie, jak niektóre inne odpowiedzi, ale uważam to za podstawową funkcję, więc wydajność jest ważniejsza niż posiadanie „płynnego”, „jednowierszowego” kodu, który ukrywa nieefektywność.

  • Obsługuje wiele wartości na klucz. (Sam tego nie potrzebowałem, tylko po to, żeby uciszyć 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();
    }

Przykładowe użycie

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

Wynik

http://example.com/stuff?x=1&y=2&foo=bar&foo=baz&special%20chars=%3F%20%3D%20%26

Podoba mi się, że nie korzysta z HttpUtility, który znajduje się w System.Web i nie jest dostępny wszędzie.
Kugel

+1 za nie używanie linq i nie używanie HttpUtility. Utworzyłbym puste sb i porzuciłbym zmienną „najpierw bool”, a następnie w pętli po prostu miałbym sb.Append (sb.Length == 0? ”?”: „&”) Przed sb.AppendFormat (). Teraz, jeśli nvc jest pusty, metoda zwróci pusty ciąg zamiast samotnego „?”.
Mathew Leger

Ta odpowiedź obsługuje pojedyncze parametry z wieloma wartościami. np.? id = 1 i id = 3 i id = 2 i id = 9
Mathemats

12

Co powiesz na tworzenie metod rozszerzeń, które pozwalają dodawać parametry w płynnym stylu, takim jak ten?

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

Oto przeciążenie, które wykorzystuje 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);
}

A oto przeciążenie, które wykorzystuje 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));
}

: +1: dla prostej metody rozszerzenia opartej na łańcuchach. Niektóre z pozostałych odpowiedzi może obejmować więcej przypadków krawędzi, ale ta jest wystarczająca do mojej sytuacji i nie wymaga mi skonstruować NameValueCollection, HttpValueCollectionalbo Uripierwszy. Dzięki!
Stanley G.

11

Musiałem rozwiązać ten sam problem dla przenośnej biblioteki klas (PCL), nad którą pracuję. W takim przypadku nie mam dostępu do System.Web, więc nie mogę używać ParseQueryString.

Zamiast tego użyłem System.Net.Http.FormUrlEncodedContenttak:

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

url.Query = new FormUrlEncodedContent(new Dictionary<string,string>()
{
    {"param1", "val1"},
    {"param2", "val2"},
    {"param3", "val3"},
}).ReadAsStringAsync().Result;

Jest to technika, której używam i do której odwołałem się w innym pytaniu http://stackoverflow.com/a/26744471/2108310 . Jedyną różnicą jest to, że używam tablicy par KeyValue ... poza potrzebą odniesienia do Systemu. Net (który jest dostępny PCL, jak powiedziałeś) to jest najprostszy sposób, aby to zrobić bez dołączania pakietu innej firmy lub próbowania spulchniania jakiegoś domowego spaghetti bałaganu.
Rostów

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

1
Miły! Ale nie potrzebujesz .ToArray()s.
mpen

7

Dodaj tę klasę do swojego projektu

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

I użyj tego w ten sposób:

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"

To powinna być zaakceptowana odpowiedź; działa idealnie dla tablic takich jak „foo [] = 1, foo [] = 2”, a także utrzymuje kolejność parametrów, co jest bardzo ważne.
Soroush Falahati

6

Moja propozycja:

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

Stosowanie:

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

// http://stackoverflow.com?such=method&wow=soFluent

Wolę twoje podejście Proste i eleganckie, jednak HttpUtility wymaga System.Web
Ody

5

Nie przetestowano, ale myślę, że coś w tym stylu działałoby całkiem nieźle

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

ładnie zamknięty, ale ten format na „? {0}” jest trochę niepotrzebnie drogi :)
annakata

zmienił nazwę klasy na QueryString. Zapytanie jest trochę niejednoznaczne
Nick Allen

4

Wersja oparta na metodzie szybkiego rozszerzenia:

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

Możesz użyć klauzuli where, aby wybrać parametry, które zostaną dodane do ciągu.


3

Zakładając, że chcesz zmniejszyć zależności do innych zespołów i uprościć sprawę, możesz:

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

Działa to również dobrze z pętlami. Ostateczne usunięcie ampersand musi wyjść poza pętlę.

Należy zauważyć, że operator konkatenacji służy do poprawy czytelności. Koszt korzystania z niego w porównaniu z kosztem korzystania z StringBuilder jest minimalny (myślę, że Jeff Atwood opublikował coś na ten temat).


3

W połączeniu najlepsze odpowiedzi, aby utworzyć anonimową wersję obiektu :

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

To generuje:

klucz2 = wartość2 i klucz1 = wartość1

Oto kod:

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

2

Taki sam jak przyjęte rozwiązanie, ale przeniesiony do składni LINQ „kropka” ...

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

2

Mam metodę rozszerzenia dla Uri, która:

  • Akceptuje anonimowe obiekty: uri.WithQuery(new { name = "value" })
  • Akceptuje kolekcje string/stringpar (np. Dictionary`2 ).
  • Akceptuje kolekcje string/objectpar (np. RouteValueDictionary ).
  • Akceptuje NameValueCollection s.
  • Sortuje wartości zapytania według klucza, aby te same wartości generowały jednakowe identyfikatory URI.
  • Obsługuje wiele wartości na klucz, zachowując ich pierwotną kolejność.

Udokumentowaną wersję można znaleźć tutaj .

Rozszerzenie:

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

Analizator składni zapytań:

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

Oto testy:

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

2

Klasa opakowującego łańcucha dla 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();
        }
    }
}

Przykładowe użycie:

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

1

Dodałem następującą metodę do mojej klasy PageBase.

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

Zadzwonić:

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

1

Napisałem kilka metod rozszerzenia, które okazały się bardzo przydatne podczas pracy z QueryStrings. Często chcę zacząć od bieżącego QueryString i zmodyfikować przed użyciem. Coś jak,

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

Więcej i źródło: http://www.charlesrcook.com/archive/2008/07/23/c-extension-methods-for-asp.net-query-string-operations.aspx

To podstawowe, ale podoba mi się styl.


1

Chciałem tylko wrzucić moje 2 centy:

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

Dokumenty mówią, że uri.Queryzacznie się od? jeśli nie jest pusty i powinieneś go przyciąć, jeśli chcesz go zmodyfikować.

Uwaga, która HttpUtility.UrlEncodeznajduje się w System.Web.

Stosowanie:

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

1

Chociaż nie jest elegancki, zdecydowałem się na prostszą wersję, która nie używa NameValueCollecitons- po prostu owinięty wzór konstruktora 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
}

Przy istniejących odpowiedziach upewniłem się, że korzystam z HttpUtility.UrlEncodepołączeń. Używa się go tak:

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

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

Wynik:

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

Kod; wszyscy możecie gdzieś podziękować: 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);
        }
    }
}

1

Poszedłem z rozwiązaniem zaproponowanym przez DSO (odpowiedź 2 sierpnia 11 o 7:29), jego rozwiązanie nie wymaga użycia HttpUtility. Jednak zgodnie z artykułem opublikowanym w Dotnetpearls korzystanie ze słownika jest szybsze (pod względem wydajności) niż korzystanie z NameValueCollection. Oto rozwiązanie DSO zmodyfikowane do używania Dictionary zamiast 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();
    }

1

Ciąg zapytania można dodać do adresu URL poprzez:

  1. utwórz obiekt kolekcji wartości nazw
  2. dodaj elementy ciągu zapytania i ich wartości do tego obiektu
  3. zakoduj ten obiekt kolekcji wartości nazwy do adresu URL kod znajduje się w poniższym linku

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

0

Napisałem pomocnika do mojego projektu maszynki do golenia, korzystając z niektórych wskazówek z innych odpowiedzi.

Firma ParseQueryString jest konieczna, ponieważ nie wolno nam manipulować obiektem QueryString bieżącego żądania.

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

Używam tego w ten sposób:

location.search = '?@Helpers.GetQueryStringWithValue("var-name", "var-value")';

Jeśli chcesz, aby przyjmował więcej niż jedną wartość, po prostu zmień parametry na Słownik i dodaj pary do ciągu zapytania.


0

Poniższy kod jest usuwany z HttpValueCollectionimplementacji za ToStringpośrednictwem ILSpy, co daje kwerendę nazwa = wartość.

Niestety HttpValueCollection jest klasą wewnętrzną, którą możesz odzyskać tylko wtedy, gdy używasz HttpUtility.ParseQueryString(). Usunąłem wszystkie części viewstate i domyślnie koduje:

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

0

Jest to identyczna z przyjętą odpowiedzią, z wyjątkiem nieco bardziej zwartej:

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

0

Tylko dla tych, którzy potrzebują najpopularniejszej wersji 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

A wersja bez 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

I wersja C # bez 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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.