c# - Versión de la biblioteca de clases portátil(PCL) de HttpUtility.ParseQueryString
asp.net .net (4)
¿Existe una versión de Portable Class Library (PCL) de HttpUtility.ParseQueryString contenida en System.Web o algún código que pueda usar? Quiero leer una URL muy compleja.
Hice un paquete nuget hoy que hace la construcción básica de consultas y el análisis. Está hecho para uso personal pero está disponible en el repositorio de nuget.com. Para uso personal significa que puede no ser totalmente compatible con las ''especificaciones de consulta http''. Nuget enlace aquí
Se basa en un diccionario, por lo que no admite claves duplicadas, principalmente porque no sé por qué querría eso ... (¿alguien puede iluminarme?)
Tiene 1 clase que representa una consulta que admite agregar, obtener parámetros, verificar si contiene una clave ... Y un método estático para analizar una clave y devolver una instancia de consulta.
Mi biblioteca Flurl es una PCL que analiza las cadenas de consulta en IDictionary<string, object>
cuando crea una instancia de un objeto Url
desde una cadena:
using Flurl;
var url = new Url("http://...");
// get values from url.QueryParams dictionary
La lógica de análisis relevante está here . Flurl es pequeño, pero siéntete libre de deslizar solo esos bits si quieres.
También puedes implementarlo así:
public static class HttpUtility
{
public static Dictionary<string, string> ParseQueryString(Uri uri)
{
var query = uri.Query.Substring(uri.Query.IndexOf(''?'') + 1); // +1 for skipping ''?''
var pairs = query.Split(''&'');
return pairs
.Select(o => o.Split(''=''))
.Where(items => items.Count() == 2)
.ToDictionary(pair => Uri.UnescapeDataString(pair[0]),
pair => Uri.UnescapeDataString(pair[1]));
}
}
Aquí hay una prueba de unidad para eso:
public class HttpParseQueryValuesTests
{
[TestCase("http://www.example.com", 0, "", "")]
[TestCase("http://www.example.com?query=value", 1, "query", "value")]
public void When_parsing_http_query_then_should_have_these_values(string uri, int expectedParamCount,
string expectedKey, string expectedValue)
{
var queryParams = HttpUtility.ParseQueryString(new Uri(uri));
queryParams.Count.Should().Be(expectedParamCount);
if (queryParams.Count > 0)
queryParams[expectedKey].Should().Be(expectedValue);
}
}
HttpUtility.ParseQueryString
devuelve HttpValueCollection
(Clase interna) que hereda de NameValueCollection
. NameValueCollection
es una colección de pares de valores clave como un diccionario pero admite duplicados, mantiene el orden y solo implementa IEnumerable
(esta colección es pre-generica). NameValueCollection
no es compatible con PCL.
Mi solución (parcialmente levantada y modificada del marco .NET) es sustituir HttpValueCollection con Collection<HttpValue>
donde HttpValue
es solo un par de valores clave.
public sealed class HttpUtility
{
public static HttpValueCollection ParseQueryString(string query)
{
if (query == null)
{
throw new ArgumentNullException("query");
}
if ((query.Length > 0) && (query[0] == ''?''))
{
query = query.Substring(1);
}
return new HttpValueCollection(query, true);
}
}
public sealed class HttpValue
{
public HttpValue()
{
}
public HttpValue(string key, string value)
{
this.Key = key;
this.Value = value;
}
public string Key { get; set; }
public string Value { get; set; }
}
public class HttpValueCollection : Collection<HttpValue>
{
#region Constructors
public HttpValueCollection()
{
}
public HttpValueCollection(string query)
: this(query, true)
{
}
public HttpValueCollection(string query, bool urlencoded)
{
if (!string.IsNullOrEmpty(query))
{
this.FillFromString(query, urlencoded);
}
}
#endregion
#region Parameters
public string this[string key]
{
get { return this.First(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase)).Value; }
set { this.First(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase)).Value = value; }
}
#endregion
#region Public Methods
public void Add(string key, string value)
{
this.Add(new HttpValue(key, value));
}
public bool ContainsKey(string key)
{
return this.Any(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase));
}
public string[] GetValues(string key)
{
return this.Where(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase)).Select(x => x.Value).ToArray();
}
public void Remove(string key)
{
this.Where(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase))
.ToList()
.ForEach(x => this.Remove(x));
}
public override string ToString()
{
return this.ToString(true);
}
public virtual string ToString(bool urlencoded)
{
return this.ToString(urlencoded, null);
}
public virtual string ToString(bool urlencoded, IDictionary excludeKeys)
{
if (this.Count == 0)
{
return string.Empty;
}
StringBuilder stringBuilder = new StringBuilder();
foreach (HttpValue item in this)
{
string key = item.Key;
if ((excludeKeys == null) || !excludeKeys.Contains(key))
{
string value = item.Value;
if (urlencoded)
{
// If .NET 4.5 and above (Thanks @Paya)
key = WebUtility.UrlDecode(key);
// If .NET 4.0 use this instead.
// key = Uri.EscapeDataString(key);
}
if (stringBuilder.Length > 0)
{
stringBuilder.Append(''&'');
}
stringBuilder.Append((key != null) ? (key + "=") : string.Empty);
if ((value != null) && (value.Length > 0))
{
if (urlencoded)
{
value = Uri.EscapeDataString(value);
}
stringBuilder.Append(value);
}
}
}
return stringBuilder.ToString();
}
#endregion
#region Private Methods
private void FillFromString(string query, bool urlencoded)
{
int num = (query != null) ? query.Length : 0;
for (int i = 0; i < num; i++)
{
int startIndex = i;
int num4 = -1;
while (i < num)
{
char ch = query[i];
if (ch == ''='')
{
if (num4 < 0)
{
num4 = i;
}
}
else if (ch == ''&'')
{
break;
}
i++;
}
string str = null;
string str2 = null;
if (num4 >= 0)
{
str = query.Substring(startIndex, num4 - startIndex);
str2 = query.Substring(num4 + 1, (i - num4) - 1);
}
else
{
str2 = query.Substring(startIndex, i - startIndex);
}
if (urlencoded)
{
this.Add(Uri.UnescapeDataString(str), Uri.UnescapeDataString(str2));
}
else
{
this.Add(str, str2);
}
if ((i == (num - 1)) && (query[i] == ''&''))
{
this.Add(null, string.Empty);
}
}
}
#endregion
}
ACTUALIZAR
Se actualizó para que HttpValueCollection ahora se hereda de la Colección en lugar de la Lista como se resalta en los comentarios
ACTUALIZACIÓN 2
Actualizado para usar WebUtility.UrlDecode si usa .NET 4.5, gracias a @Paya.