c# - getasync - Cadena de consulta de compilación para System.Net.HttpClient get
using httpclient postasync c# (14)
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
.
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
Darin ofreció una solución interesante e inteligente, y aquí hay algo que puede ser otra opción:
public class ParameterCollection
{
private Dictionary<string, string> _parms = new Dictionary<string, string>();
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;
En un proyecto ASP.NET Core puede usar la clase QueryHelpers.
// using Microsoft.AspNetCore.WebUtilities;
var query = new Dictionary<string, string>
{
["foo"] = "bar",
["foo2"] = "bar2",
// ...
};
var response = await client.GetAsync(QueryHelpers.AddQueryString("/api/", query));
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<TResult>();
Consulte Flurl 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
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<string, string> parameters)
{
var stringBuilder = new StringBuilder();
string str = "?";
foreach (KeyValuePair<string, string> parameter in parameters)
{
stringBuilder.Append(str + parameter.Key + "=" + parameter.Value);
str = "&";
}
return uri + stringBuilder;
}
}
Uso
var parameters = new Dictionary<string, string>();
parameters.Add("Bill", "Gates");
parameters.Add("Steve", "Jobs");
string uri = "http://www.example.com/index.php".AttachParameters(parameters);
Resultado
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<string,string> 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<string,string> 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();
}
}
La biblioteca de plantillas de URI de RFC 6570 que estoy desarrollando es capaz de realizar esta operación. Toda la codificación 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<string, string>
{
{ "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<string, string>
{
{ "param1", "value1" },
{ "param2", "value2" },
};
Uri baseAddress = new Uri("http://www.example.com");
Uri relativeUri = template.BindByName(baseAddress, parameters);
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<TKey, TValue>(this Dictionary<TKey, TValue> dictionary)
{
return ToQueryString<TKey, TValue>(dictionary, "?");
}
public static string ToQueryString<TKey, TValue>(this Dictionary<TKey, TValue> 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<string, string>
{
{ "param1", "value1" },
{ "param2", "value2" },
};
param.ToQueryString(); //By default will add (?) question mark at begining
//"?param1=value1¶m2=value2"
param.ToQueryString("&"); //Will add (&)
//"¶m1=value1¶m2=value2"
param.ToQueryString(""); //Won''t add anything
//"param1=value1¶m2=value2"
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
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:
string query;
using(var content = new FormUrlEncodedContent(new KeyValuePair<string, string>[]{
new KeyValuePair<string, string>("ham", "Glazed?"),
new KeyValuePair<string, string>("x-men", "Wolverine + Logan"),
new KeyValuePair<string, string>("Time", DateTime.UtcNow.ToString()),
})) {
query = content.ReadAsStringAsync().Result;
}
Para evitar el problema de codificación 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()
.
Siempre puede usar IEnterprise.Easy-HTTP porque tiene un constructor de consultas integrado:
await new RequestBuilder<ExampleObject>()
.SetHost("https://httpbin.org")
.SetContentType(ContentType.Application_Json)
.SetType(RequestType.Get)
.ContinueToQuery()
.SetQuery("/get")
.ParseModelToQuery(dto)
.Build()
.Build()
.Execute();
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 extraño problema de doble codificación con la solución aceptada:
Entonces, si está tratando con caracteres que necesitan ser codificados, la solución aceptada conduce a una doble codificación:
- los parámetros de consulta se
NameValueCollection
utilizando el indexadorNameValueCollection
( y esto usaUrlEncodeUnicode
, no es unUrlEncode
esperado (!) ) - Entonces, cuando llamas a
uriBuilder.Uri
crea un nuevoUri
usando un constructor que codifica una vez más (codificación url normal) - Eso no se puede evitar haciendo
uriBuilder.ToString()
(aunque esto devuelveUri
correcto, que IMO es al menos incoherente, tal vez un error, pero esa es otra pregunta) y luego usando el métodoHttpClient
aceptando cadena - el cliente todavía creaUri
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 codificación 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í