Como criar uma string de consulta para um URL em c #?

473

Uma tarefa comum ao chamar recursos da Web a partir de um código é criar uma sequência de consultas para incluir todos os parâmetros necessários. Embora, por todos os meios, não exista ciência de foguetes, há alguns detalhes bacanas que você precisa cuidar, acrescentando um &se não o primeiro parâmetro, codificando os parâmetros etc.

O código para fazer isso é muito simples, mas um pouco tedioso:

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

Essa é uma tarefa tão comum que se espera que exista uma classe de utilidade que a torne mais elegante e legível. Ao digitalizar o MSDN, não encontrei um - o que me leva à seguinte pergunta:

Qual é a maneira mais elegante e limpa de saber o que foi dito acima?

Boaz
fonte
26
É um pouco triste que, mesmo no momento atual, pareça não haver uma maneira direta de lidar com as strings de consulta. E, direto, quero dizer uma classe de estrutura OOB, não interna, compatível com padrões. Ou talvez eu esteja perdendo alguma coisa?
Careta do Desespero
5
Você não está perdendo nada. A construção de consultas é uma grande lacuna na estrutura que tentei preencher com o Flurl .
Todd Menier
Você acabou de me fazer pensar que eu deveria criar um .. new UrlBuilder (existente) .AddQuery ("key", "value"). ToString ()
Demetris Leptos

Respostas:

293

Se você olhar por baixo do capô, a propriedade QueryString é uma NameValueCollection. Quando eu faço coisas semelhantes, geralmente me interesso em serializar E desserializar, então minha sugestão é criar uma NameValueCollection e passar para:

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

Eu imagino que há uma maneira super elegante de fazer isso no LINQ também ...

annakata
fonte
22
A especificação HTTP (RFC 2616) não diz nada sobre o que as strings de consulta podem conter. Nem o RFC 3986, que define o formato URI genérico. O formato de par de chave / valor que é comumente usado é chamado application/x-www-form-urlencodede é realmente definido pelo HTML, com a finalidade de enviar dados do formulário como parte de uma GETsolicitação. O HTML 5 não proíbe vários valores por chave nesse formato e, de fato, exige que o navegador produza vários valores por chave, caso a página (incorretamente) contenha vários campos com o mesmo nameatributo. Veja goo.gl/uk1Ag
Daniel Cassidy
14
@annakata: Eu sei que meu comentário tem mais de um ano (e a resposta tem mais de dois anos!), mas NameValueCollection suporta muito vários valores por chave, usando o método GetValues ​​(key).
Mauricio Scheffer
4
@MauricioScheffer: Mas NameValueCollection não se transforma em uma string de consulta "corretamente". Por exemplo, se você definir o parâmetro QueryString no WebClient em que a mesma chave estiver presente várias vezes, ele se transformará em "caminho? Chave = valor1, valor2" em vez de "caminho? Chave = valor1 & chave = valor2", o que é comum (padrão ?) padronizar.
David Pope
8
Com relação a vários valores por chave, acredito que, em HTML, se você tiver uma caixa de listagem de seleção múltipla com vários itens selecionados e enviados, eles serão enviados no formato de valores múltiplos mencionado por David.
10243 Sam
10
Convém usar Uri.EscapeDataString em vez de HttpUtility.UrlEncode, que é mais portátil. Veja stackoverflow.com/questions/2573290/…
PEK
687

Você pode criar uma nova instância gravável HttpValueCollectionchamando System.Web.HttpUtility.ParseQueryString(string.Empty)e usá-la como qualquer outra NameValueCollection. Depois de adicionar os valores desejados, você pode chamar ToStringa coleção para obter uma sequência de consultas, da seguinte maneira:

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

O HttpValueCollectioné interno e, portanto, você não pode construir diretamente uma instância. No entanto, depois de obter uma instância, você pode usá-la como qualquer outra NameValueCollection. Como o objeto real com o qual você está trabalhando é um HttpValueCollection, chamar o método ToString chamará o método substituído HttpValueCollection, que formata a coleção como uma sequência de consulta codificada em URL.

Depois de pesquisar na SO e na Web por uma resposta para um problema semelhante, esta é a solução mais simples que encontrei.

.NET Core

Se você estiver trabalhando no .NET Core, poderá usar a Microsoft.AspNetCore.WebUtilities.QueryHelpersclasse, o que simplifica bastante isso.

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

Código de amostra:

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
fonte
6
Você provavelmente poderia criar um método de extensão chamado ToURLQueryString para a interface IDictionary:public static string ToURLQueryString(this IDictionary dict) { ... }
Roy Tinker
65
Este método não é compatível com o padrão para caracteres multibyte. Ele os codificará como% uXXXX em vez de% XX% XX. As seqüências de consulta resultantes podem ser interpretadas incorretamente pelos servidores da web. Isso é documentado na classe de estrutura interna HttpValueCollection retornada por HttpUtility.ParseQueryString (). O comentário diz que eles mantêm esse comportamento por motivos de compatibilidade com versões anteriores.
alex
25
Observe que há uma diferença importante entre HttpUtilityPraseQueryString ("") e novo NameValueCollection () - somente o resultado HttpUtility substituirá ToString () para produzir uma
string de consulta
7
E os casos em que você deseja várias instâncias de um nome na string de consulta? Por exemplo, "type = 10 & type = 21".
Finster
7
@Finster Você pode adicionar várias instâncias de um nome à string de consulta usando o Addmétodo Ou seja, queryString.Add("type", "1"); queryString.Add("type", "2"); usar o Addmétodo é provavelmente a melhor maneira de fazer isso o tempo todo, na verdade.
jeremysawesome
94

Com a inspiração do comentário de Roy Tinker, acabei usando um método de extensão simples na classe Uri que mantém meu código conciso e limpo:

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

Uso:

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

Editar - Variante compatível com padrões

Como várias pessoas apontaram, httpValueCollection.ToString()codifica caracteres Unicode de uma maneira não compatível com os padrões . Essa é uma variante do mesmo método de extensão que lida com esses caracteres, invocando o HttpUtility.UrlEncodemétodo em vez do HttpUtility.UrlEncodeUnicodemétodo obsoleto .

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
fonte
3
Perfeito. Adicionado à minha biblioteca interna. :)
Andy
1
Você também deve codificar por URL o valor. queryString.Add (nome, Uri.EscapeDataString (valor));
Ufuk Hacıoğulları
2
Obrigado por melhorar esta resposta. Corrigido o problema com caracteres multibyte.
Ufuk Hacıoğulları
9
Observação: isso não funciona com URLs relativos, porque você não pode instanciar o UriBuilder a partir de um URL relativo.
Yuriy Faktorovich
1
Eu adicionei uma chave de remoção para que uma duplicata não possa ser adicionada. dotnetfiddle.net/hTlyAd
Paul Totzke
29

Eu respondi uma pergunta semelhante há um tempo atrás. Basicamente, a melhor maneira seria usar a classe HttpValueCollection, que é Request.QueryStringrealmente a propriedade do ASP.NET , infelizmente ela é interna na estrutura do .NET. Você pode usar o Reflector para agarrá-lo (e colocá-lo na sua classe Utils). Dessa forma, você pode manipular a sequência de consultas como um NameValueCollection, mas com todos os problemas de codificação / decodificação de URL atendidos.

HttpValueCollectionestende NameValueCollectione possui um construtor que utiliza uma sequência de consulta codificada (e comercial e pontos de interrogação incluídos) e substitui umToString() método para reconstruir posteriormente a sequência de consultas da coleção subjacente.

Exemplo:

  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
fonte
Obrigado ... notei que o NameValueCollection retorna possui um ToString () que age de maneira diferente, mas não conseguiu descobrir o porquê.
calebt
httpValueCollection.ToString()na verdade chama, httpValueCollection.ToString(true)portanto, truenão é necessário adicionar explicitamente.
David_i
5
HttpValueCollection é uma classe interna, portanto, você não pode instancia-la.
ozba
29

Aqui está uma maneira fluente / lambda-ish como um método de extensão (combinando conceitos em postagens anteriores) que suporta vários valores para a mesma chave. Minha preferência pessoal é extensões sobre invólucros para a capacidade de descoberta por outros membros da equipe para coisas como esta. Observe que há controvérsia quanto aos métodos de codificação, muitas postagens sobre isso no Stack Overflow (uma dessas postagens ) e blogueiros do MSDN (como esta ).

public static string ToQueryString(this NameValueCollection source)
{
    return String.Join("&", source.AllKeys
        .SelectMany(key => source.GetValues(key)
            .Select(value => String.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(value))))
        .ToArray());
}

edit: com suporte nulo, embora você provavelmente precise adaptá-lo para sua situação específica

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
fonte
1
Isso falhará se algum dos valores for nulo
Josh Noe
isso é errado que geram muitas seqüências de consulta para cada par de valores-chave
Gayan
@GayanRanasinghe: O que isso significa?
Matti Virkkunen
22

Flurl [divulgação: eu sou o autor] suporta a construção de cadeias de consulta por meio de objetos anônimos (entre outras maneiras):

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

A lib complementar opcional Flurl.Http permite fazer chamadas HTTP diretamente da mesma cadeia de chamadas fluente, estendendo-a para um cliente REST completo:

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

O pacote completo está disponível no NuGet:

PM> Install-Package Flurl.Http

ou apenas o construtor de URL independente:

PM> Install-Package Flurl

Todd Menier
fonte
20

Aqui está minha entrada tardia. Eu não gostei de nenhum dos outros por várias razões, então escrevi o meu.

Esta versão possui:

  • Uso apenas de StringBuilder. Nenhuma chamada ToArray () ou outros métodos de extensão. Não parece tão bonito quanto algumas das outras respostas, mas considero essa uma função essencial, portanto a eficiência é mais importante do que ter um código "fluente" e "uma linha" que oculte ineficiências.

  • Manipula vários valores por chave. (Não precisava disso sozinho, mas apenas para silenciar 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();
    }

Exemplo de uso

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

Resultado

http://example.com/stuff?x=1&y=2&foo=bar&foo=baz&special%20chars=%3F%20%3D%20%26
DSO
fonte
Eu gosto que isso não use HttpUtility, que está no System.Web e não está disponível em todos os lugares.
Kugel
+1 por não usar linq e não usar HttpUtility. Eu criaria um sb vazio e abandonaria a variável "bool first" e, em seguida, no loop simplesmente teria sb.Append (sb.Length == 0? "?": "&") Antes do sb.AppendFormat (). Agora, se o nvc estiver vazio, o método retornará uma string vazia em vez de um "?" Solitário.
Mathew Leger
Esta resposta lida com parâmetros únicos com vários valores. Por exemplo,? id = 1 & id = 3 & id = 2 & id = 9
Mathemats
12

Que tal criar métodos de extensão que permitam adicionar os parâmetros em um estilo fluente como este?

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

Aqui está a sobrecarga que usa um 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);
}

E aqui está a sobrecarga que usa um 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
fonte
: +1: para o método de extensão simples baseado em String. Algumas das outras respostas podem abranger mais casos extremos, mas isso é suficiente para a minha situação, e não exigir-me a construir um NameValueCollection, HttpValueCollectionou um Uriem primeiro lugar. Obrigado!
Stanley G.
11

Eu precisava resolver o mesmo problema para uma biblioteca de classes portátil (PCL) em que estou trabalhando. Nesse caso, não tenho acesso ao System.Web, portanto não posso usar ParseQueryString.

Em vez disso, usei System.Net.Http.FormUrlEncodedContentassim:

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

url.Query = new FormUrlEncodedContent(new Dictionary<string,string>()
{
    {"param1", "val1"},
    {"param2", "val2"},
    {"param3", "val3"},
}).ReadAsStringAsync().Result;
hortman
fonte
Essa é a técnica que eu uso e a referenciei em outra pergunta http://stackoverflow.com/a/26744471/2108310 A única diferença é que eu uso uma matriz de pares KeyValue ... além de precisar da referência ao sistema. Net (que está disponível como PCL, como você declarou), esta é a maneira mais simples de fazê-lo, sem incluir um pacote de terceiros, ou tentar manchar alguma bagunça de espaguete caseira.
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
fonte
1
Agradável! Mas você não precisa do .ToArray()s.
MPEN
7

Adicione esta classe ao seu projeto

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

E use-o assim:

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
fonte
Agora, essa deve ser a resposta aceita; funciona perfeitamente para matrizes como "foo [] = 1, foo [] = 2", além de manter a ordem dos parâmetros que são muito importantes por sinal.
Soroush Falahati
6

Minha oferta:

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

Uso:

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

// http://stackoverflow.com?such=method&wow=soFluent
dav_i
fonte
Eu prefiro a sua abordagem simples e elegante, no entanto, HttpUtility requer System.Web
Ody
5

Não testado, mas acho que algo nesse sentido funcionaria muito bem

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
fonte
bem encapsulado, mas esse formato on é uma espécie de desnecessariamente caro :) "{0}?"
annakata
mudou o nome da classe para QueryString .. Consulta é um pouco ambígua
Nick Allen
4

Uma versão baseada no método de extensão rápida:

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

Você pode usar uma cláusula where para selecionar quais parâmetros serão adicionados à string.

Martin Harris
fonte
3

Supondo que você queira reduzir dependências para outros assemblies e manter as coisas simples, você pode:

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

Isso funciona bem com loops também. A remoção final do e comercial precisa sair do loop.

Observe que o operador de concatenação é usado para melhorar a legibilidade. O custo de usá-lo em comparação com o custo de um StringBuilder é mínimo (acho que Jeff Atwood postou algo sobre este tópico).

Thomas Bratt
fonte
3

Combinou as principais respostas para criar uma versão anônima do objeto :

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

Isso gera isso:

key2 = valor2 & key1 = valor1

Aqui está o código:

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
fonte
2

O mesmo que a solução aceita, mas transfere para a sintaxe LINQ "ponto" ...

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
fonte
2

Eu tenho um método de extensão para Uri que:

  • Aceita objetos anônimos: uri.WithQuery(new { name = "value" })
  • Aceita coleções de string/stringpares (por exemplo, Dictionary`2 ).
  • Aceita coleções de string/objectpares (por exemplo, RouteValueDictionary ).
  • Aceita NameValueCollection s.
  • Classifica os valores da consulta por chave para que os mesmos valores produzam URIs iguais.
  • Suporta vários valores por chave, preservando a ordem original.

A versão documentada pode ser encontrada aqui .

A extensão:

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

O analisador de consultas:

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

Aqui estão os testes:

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
fonte
2

Classe de wrapper capaz de cadeia para 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();
        }
    }
}

Exemplo de uso:

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

Eu adicionei o seguinte método à minha classe 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());
    }

Chamar:

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

Eu escrevi alguns métodos de extensão que achei muito úteis ao trabalhar com QueryStrings. Freqüentemente, quero começar com o QueryString atual e modificar antes de usá-lo. Algo como,

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

Para mais informações e a fonte: http://www.charlesrcook.com/archive/2008/07/23/c-extension-methods-for-asp.net-query-string-operations.aspx

É básico, mas eu gosto do estilo.

ccook
fonte
1

Só queria jogar meus 2 centavos:

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

Os documentos dizem que uri.Querycomeçará com um? se não estiver vazio e você deve cortá-lo se quiser modificá-lo.

Observe que HttpUtility.UrlEncodeé encontrado em System.Web.

Uso:

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

Embora não seja elegante, optei por uma versão mais simples que não usa NameValueCollecitons- apenas um padrão de construtor 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
}

Pelas respostas existentes, certifiquei-me de usar as HttpUtility.UrlEncodechamadas. É usado assim:

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

Resultado:

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

O código; todos vocês podem me agradecer em algum lugar, de alguma forma: 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
fonte
1

Fui com a solução proposta pelo DSO (respondida em 2 de agosto de 11 às 7:29), sua solução não requer o uso de HttpUtility. No entanto, de acordo com um artigo publicado no Dotnetpearls , o uso de um dicionário é mais rápido (em desempenho) do que o uso de NameValueCollection. Aqui está a solução do DSO modificada para usar o Dicionário no lugar de 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
fonte
1

A cadeia de consulta pode ser adicionada a um URL por:

  1. crie um objeto de coleção de valor de nome
  2. adicione os itens da string de consulta e seus valores a esse objeto
  3. codifique esse objeto de coleta de valor de nome para o URL, o código é fornecido no link abaixo

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);
}
trabalho
fonte
0

Eu escrevi um ajudante para o meu projeto de barbear usando algumas dicas de outras respostas.

O negócio ParseQueryString é necessário porque não temos permissão para violar o objeto QueryString da solicitação atual.

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

Eu uso assim:

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

Se você deseja que ele use mais de um valor, basta alterar os parâmetros para um Dicionário e adicionar os pares à string de consulta.

LOAS
fonte
0

O código abaixo é retirado da HttpValueCollectionimplementação ToString, via ILSpy, que fornece uma string de consulta name = value.

Infelizmente, HttpValueCollection é uma classe interna que você só recebe de volta se usar HttpUtility.ParseQueryString(). Eu removi todas as partes do viewstate e ele codifica por padrão:

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
fonte
0

É o mesmo que a resposta aceita, exceto um pouco mais compacta:

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

Apenas para aqueles que precisam da versão VB.NET da resposta principal:

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

E a versão sem 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

E a versão C # sem 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
fonte