Cadena de consulta de comstackción para System.Net.HttpClient get

Si deseo enviar una solicitud de obtención de HTTP utilizando System.Net.HttpClient, parece que no hay API para agregar parámetros, ¿es correcto?

¿Hay alguna API simple disponible para construir la cadena de consulta que no implique construir una colección de valores de nombre y url que los codifique y finalmente concatenarlos? Esperaba usar algo como la api de RestSharp (es decir, AddParameter (..))

Si deseo enviar una solicitud de obtención de HTTP utilizando System.Net.HttpClient, parece que no hay API para agregar parámetros, ¿es correcto?

Sí.

¿Hay alguna API simple disponible para construir la cadena de consulta que no implique construir una colección de valores de nombre y url que los codifique y finalmente concatenarlos?

Por supuesto:

var query = HttpUtility.ParseQueryString(string.Empty); query["foo"] = "bar<>&-baz"; query["bar"] = "bazinga"; string queryString = query.ToString(); 

le dará el resultado esperado:

 foo=bar%3c%3e%26-baz&bar=bazinga 

También puede encontrar útil la clase UriBuilder :

 var builder = new UriBuilder("http://example.com"); builder.Port = -1; var query = HttpUtility.ParseQueryString(builder.Query); query["foo"] = "bar<>&-baz"; query["bar"] = "bazinga"; builder.Query = query.ToString(); string url = builder.ToString(); 

le dará el resultado esperado:

 http://example.com/?foo=bar%3c%3e%26-baz&bar=bazinga 

que podría más que alimentar con seguridad a su método HttpClient.GetAsync .

Para aquellos que no desean incluir System.Web en proyectos que aún no lo usan, puede usar System.Net.Http y hacer algo como lo siguiente:

versión de keyvaluepair

 string query; using(var content = new FormUrlEncodedContent(new KeyValuePair[]{ new KeyValuePair("ham", "Glazed?"), new KeyValuePair("x-men", "Wolverine + Logan"), new KeyValuePair("Time", DateTime.UtcNow.ToString()), })) { query = content.ReadAsStringAsync().Result; } 

versión del diccionario

 var query = new FormUrlEncodedContent(new Dictionary() { { "ham", "Glaced?"}, { "x-men", "Wolverine + Logan"}, { "Time", DateTime.UtcNow.ToString() }, }).ReadAsStringAsync().Result; 

TL; DR: no use la versión aceptada ya que está completamente rota en relación con el manejo de caracteres Unicode, y nunca use la API interna

De hecho, he encontrado un raro problema de doble encoding con la solución aceptada:

Entonces, si está tratando con caracteres que necesitan ser codificados, la solución aceptada conduce a una doble encoding:

  • los parámetros de consulta se NameValueCollection utilizando el indexador NameValueCollection ( y esto usa UrlEncodeUnicode , no es un UrlEncode esperado (!) )
  • Entonces, cuando llamas a uriBuilder.Uri crea un nuevo Uri usando un constructor que codifica una vez más (encoding url normal)
  • Eso no se puede evitar haciendo uriBuilder.ToString() (aunque esto devuelve Uri correcto, que IMO es al menos incoherente, tal vez un error, pero esa es otra pregunta) y luego usando el método HttpClient aceptando cadena – el cliente todavía crea Uri de su pasado una cadena como esta: new Uri(uri, UriKind.RelativeOrAbsolute)

Pequeño, pero completo repro:

 var builder = new UriBuilder { Scheme = Uri.UriSchemeHttps, Port = -1, Host = "127.0.0.1", Path = "app" }; NameValueCollection query = HttpUtility.ParseQueryString(builder.Query); query["cyrillic"] = "кирилиця"; builder.Query = query.ToString(); Console.WriteLine(builder.Query); //query with cyrillic stuff UrlEncodedUnicode, and that's not what you want var uri = builder.Uri; // creates new Uri using constructor which does encode and messes cyrillic parameter even more Console.WriteLine(uri); // this is still wrong: var stringUri = builder.ToString(); // returns more 'correct' (still `UrlEncodedUnicode`, but at least once, not twice) new HttpClient().GetStringAsync(stringUri); // this creates Uri object out of 'stringUri' so we still end up sending double encoded cyrillic text to server. Ouch! 

Salida:

 ?cyrillic=%u043a%u0438%u0440%u0438%u043b%u0438%u0446%u044f https://127.0.0.1/app?cyrillic=%25u043a%25u0438%25u0440%25u0438%25u043b%25u0438%25u0446%25u044f 

Como puede ver, no importa si lo hace uribuilder.ToString() + httpClient.GetStringAsync(string) o uriBuilder.Uri + httpClient.GetStringAsync(Uri) termina enviando un parámetro codificado doble

El ejemplo fijo podría ser:

 var uri = new Uri(builder.ToString(), dontEscape: true); new HttpClient().GetStringAsync(uri); 

Pero esto usa un constructor obsoleto de Uri

PD en mi última versión de .NET en Windows Server, el constructor de Uri con bool doc comment dice “obsoleto, dontEscape siempre es falso”, pero en realidad funciona como se esperaba (omite escapes)

Entonces parece otro error …

E incluso esto es completamente incorrecto: envía UrlEncodedUnicode al servidor, no solo UrlEncoded lo que el servidor espera

Actualización: una cosa más es, NameValueCollection realmente hace UrlEncodeUnicode, que ya no se debe usar y es incompatible con url.encode / decode (ver NameValueCollection a URL Query? ).

Entonces la conclusión es: nunca use este truco con NameValueCollection query = HttpUtility.ParseQueryString(builder.Query); ya que afectará tus parámetros de consulta Unicode. Simplemente construya la consulta manualmente y asígnela a UriBuilder.Query que hará la encoding necesaria y luego obtendrá Uri usando UriBuilder.Uri .

Primer ejemplo de hacerte daño usando un código que no se supone debe usarse así

Es posible que desee comprobar Flurl [revelación: soy el autor], un generador de URL fluido con lib complementario opcional que lo extiende a un cliente REST en toda regla.

 var result = await "https://api.com" // basic URL building: .AppendPathSegment("endpoint") .SetQueryParams(new { api_key = ConfigurationManager.AppSettings["SomeApiKey"], max_results = 20, q = "Don't worry, I'll get encoded!" }) .SetQueryParams(myDictionary) .SetQueryParam("q", "overwrite q!") // extensions provided by Flurl.Http: .WithOAuthBearerToken("token") .GetJsonAsync(); 

Consulte los documentos para obtener más detalles. El paquete completo está disponible en NuGet:

PM> Install-Package Flurl.Http

o solo el creador de URL independiente:

PM> Install-Package Flurl

En un proyecto ASP.NET Core puede usar la clase QueryHelpers.

 // using Microsoft.AspNetCore.WebUtilities; var query = new Dictionary { ["foo"] = "bar", ["foo2"] = "bar2", // ... }; var response = await client.GetAsync(QueryHelpers.AddQueryString("/api/", query)); 

Darin ofreció una solución interesante e inteligente, y aquí hay algo que puede ser otra opción:

 public class ParameterCollection { private Dictionary _parms = new Dictionary(); public void Add(string key, string val) { if (_parms.ContainsKey(key)) { throw new InvalidOperationException(string.Format("The key {0} already exists.", key)); } _parms.Add(key, val); } public override string ToString() { var server = HttpContext.Current.Server; var sb = new StringBuilder(); foreach (var kvp in _parms) { if (sb.Length > 0) { sb.Append("&"); } sb.AppendFormat("{0}={1}", server.UrlEncode(kvp.Key), server.UrlEncode(kvp.Value)); } return sb.ToString(); } } 

y entonces cuando lo uses, puedes hacer esto:

 var parms = new ParameterCollection(); parms.Add("key", "value"); var url = ... url += "?" + parms; 

O simplemente usando mi extensión Uri

Código

 public static Uri AttachParameters(this Uri uri, NameValueCollection parameters) { var stringBuilder = new StringBuilder(); string str = "?"; for (int index = 0; index < parameters.Count; ++index) { stringBuilder.Append(str + parameters.AllKeys[index] + "=" + parameters[index]); str = "&"; } return new Uri(uri + stringBuilder.ToString()); } 

Uso

 Uri uri = new Uri("http://www.example.com/index.php").AttachParameters(new NameValueCollection { {"Bill", "Gates"}, {"Steve", "Jobs"} }); 

Resultado

http://www.example.com/index.php?Bill=Gates&Steve=Jobs

La biblioteca de plantillas de URI de RFC 6570 que estoy desarrollando es capaz de realizar esta operación. Toda la encoding se maneja para usted de acuerdo con ese RFC. En el momento de escribir estas líneas, una versión beta está disponible y la única razón por la que no se considera una versión estable 1.0 es que la documentación no cumple completamente mis expectativas (véanse los números 17 , 18 , 32 y 43 ).

Puedes construir una cadena de consulta solo:

 UriTemplate template = new UriTemplate("{?params*}"); var parameters = new Dictionary { { "param1", "value1" }, { "param2", "value2" }, }; Uri relativeUri = template.BindByName(parameters); 

O podrías construir un URI completo:

 UriTemplate template = new UriTemplate("path/to/item{?params*}"); var parameters = new Dictionary { { "param1", "value1" }, { "param2", "value2" }, }; Uri baseAddress = new Uri("http://www.example.com"); Uri relativeUri = template.BindByName(baseAddress, parameters); 

Como tengo que reutilizar este tiempo, se me ocurrió esta clase que simplemente ayuda a abstraer cómo se compone la cadena de consulta.

 public class UriBuilderExt { private NameValueCollection collection; private UriBuilder builder; public UriBuilderExt(string uri) { builder = new UriBuilder(uri); collection = System.Web.HttpUtility.ParseQueryString(string.Empty); } public void AddParameter(string key, string value) { collection.Add(key, value); } public Uri Uri{ get { builder.Query = collection.ToString(); return builder.Uri; } } } 

El uso se simplificará a algo como esto:

 var builder = new UriBuilderExt("http://example.com/"); builder.AddParameter("foo", "bar<>&-baz"); builder.AddParameter("bar", "second"); var uri = builder.Uri; 

que devolverá el uri: http://example.com/?foo=bar%3c%3e%26-baz&bar=second

Siempre puede usar IEnterprise.Easy-HTTP porque tiene un constructor de consultas integrado:

 await new RequestBuilder() .SetHost("https://httpbin.org") .SetContentType(ContentType.Application_Json) .SetType(RequestType.Get) .ContinueToQuery() .SetQuery("/get") .ParseModelToQuery(dto) .Build() .Build() .Execute(); 

Gracias a “Darin Dimitrov”, este es el método de extensión.

  public static partial class Ext { public static Uri GetUriWithparameters(this Uri uri,Dictionary queryParams = null,int port = -1) { var builder = new UriBuilder(uri); builder.Port = port; if(null != queryParams && 0 < queryParams.Count) { var query = HttpUtility.ParseQueryString(builder.Query); foreach(var item in queryParams) { query[item.Key] = item.Value; } builder.Query = query.ToString(); } return builder.Uri; } public static string GetUriWithparameters(string uri,Dictionary queryParams = null,int port = -1) { var builder = new UriBuilder(uri); builder.Port = port; if(null != queryParams && 0 < queryParams.Count) { var query = HttpUtility.ParseQueryString(builder.Query); foreach(var item in queryParams) { query[item.Key] = item.Value; } builder.Query = query.ToString(); } return builder.Uri.ToString(); } } 

Esta es mi solución para .Net Core basada en la respuesta de Roman Ratskey. El tipo NameValueCollection se eliminó en .Net Core.

Código

 public static class UriExtensions { public static string AttachParameters(this string uri, Dictionary parameters) { var stringBuilder = new StringBuilder(); string str = "?"; foreach (KeyValuePair parameter in parameters) { stringBuilder.Append(str + parameter.Key + "=" + parameter.Value); str = "&"; } return uri + stringBuilder; } } 

Uso

  var parameters = new Dictionary(); parameters.Add("Bill", "Gates"); parameters.Add("Steve", "Jobs"); string uri = "http://www.example.com/index.php".AttachParameters(parameters); 

Resultado

http://www.example.com/index.php?Bill=Gates&Steve=Jobs

Para evitar el problema de encoding doble descrito en la respuesta de taras.roshko y para mantener la posibilidad de trabajar fácilmente con los parámetros de consulta, puede usar uriBuilder.Uri.ParseQueryString() lugar de HttpUtility.ParseQueryString() .

No pude encontrar una solución mejor que crear un método de extensión para convertir un diccionario a QueryStringFormat. La solución propuesta por Waleed AK también es buena.

Sigue mi solución:

Crea el método de extensión:

 public static class DictionaryExt { public static string ToQueryString(this Dictionary dictionary) { return ToQueryString(dictionary, "?"); } public static string ToQueryString(this Dictionary dictionary, string startupDelimiter) { string result = string.Empty; foreach (var item in dictionary) { if (string.IsNullOrEmpty(result)) result += startupDelimiter; // "?"; else result += "&"; result += string.Format("{0}={1}", item.Key, item.Value); } return result; } } 

Y ellos:

 var param = new Dictionary { { "param1", "value1" }, { "param2", "value2" }, }; param.ToQueryString(); //By default will add (?) question mark at begining //"?param1=value1&param2=value2" param.ToQueryString("&"); //Will add (&) //"&param1=value1&param2=value2" param.ToQueryString(""); //Won't add anything //"param1=value1&param2=value2"