Analizar y modificar una cadena de consulta en .NET Core

112

Me dan un URI absoluto que contiene una cadena de consulta. Estoy buscando agregar de forma segura un valor a la cadena de consulta y cambiar un parámetro existente.

Preferiría no agregar &foo=baro usar expresiones regulares, el escape de URI es complicado. Más bien, quiero usar un mecanismo incorporado que sé que hará esto correctamente y manejará el escape.

He encontrado un montón de respuestas que todo uso HttpUtility. Sin embargo, al ser ASP.NET Core, ya no hay más ensamblado System.Web, por lo tanto, no más HttpUtility.

¿Cuál es la forma adecuada de hacer esto en ASP.NET Core mientras se dirige al tiempo de ejecución del núcleo?

vcsjones
fuente
Una alternativa a Microsoft.AspNet.WebUtiltiespuede ser la Mono.HttpUtilitybiblioteca .
albañil
Escribí una publicación para el mismo, eche un vistazo aquí: neelbhatt40.wordpress.com/2017/07/06/…
Neel
2
Actualización 2017: .NET Core 2.0 incluye ahora HttpUtilityy ParseQueryStringmétodo.
KTCO

Respuestas:

152

Si está utilizando ASP.NET Core 1 o 2, puede hacerlo con Microsoft.AspNetCore.WebUtilities.QueryHelpersel paquete Microsoft.AspNetCore.WebUtilities .

Si usa ASP.NET Core 3.0 o superior, WebUtilitiesahora forma parte del SDK de ASP.NET y no requiere una referencia de paquete nuget separada.

Para analizarlo en un diccionario:

var uri = new Uri(context.RedirectUri);
var queryDictionary = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query);

Tenga ParseQueryStringen cuenta que, a diferencia de System.Web, devuelve un diccionario de tipo IDictionary<string, string[]>en ASP.NET Core 1.x, o IDictionary<string, StringValues>en ASP.NET Core 2.xo posterior, por lo que el valor es una colección de cadenas. Así es como el diccionario maneja múltiples parámetros de cadena de consulta con el mismo nombre.

Si desea agregar un parámetro a la cadena de consulta, puede usar otro método en QueryHelpers:

var parametersToAdd = new System.Collections.Generic.Dictionary<string, string> { { "resource", "foo" } };
var someUrl = "http://www.google.com";
var newUri = Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString(someUrl, parametersToAdd);

Usando .net core 2.2 puede obtener la cadena de consulta usando

var request = HttpContext.Request;
var query = request.query;
foreach (var item in query){
   Debug.WriteLine(item) 
}

Obtendrá una colección de pares clave: valor, como este

[0] {[companyName, ]}
[1] {[shop, ]}
[2] {[breath, ]}
[3] {[hand, ]}
[4] {[eye, ]}
[5] {[firstAid, ]}
[6] {[eyeCleaner, ]}
vcsjones
fuente
1
Para su información, el paquete WebUtilities no es compatible con .net core 1.0. En su Microsoft.AspNetCore.WebUtilitieslugar, puede ser necesario .
Jaime
6
@Jaime ¡Increíble observación! ¿Puedes editar mi respuesta con esa actualización para recibir crédito por ella?
vcsjones
3
Edición realizada. Manteniendo también el espacio de nombres antiguo para el caso de versiones heredadas de .net.
Jaime
1
Parece que el uso de QueryHelpers.AddQueryString automáticamente UrlEscape de las cadenas - útil.
Josh
2
El tipo de retorno ahora es IDictionary <string, StringValues> en lugar de IDictionary <string, string []>
btlog
35

La forma más fácil e intuitiva de tomar un URI absoluto y manipular su cadena de consulta usando solo paquetes ASP.NET Core, se puede hacer en unos pocos y sencillos pasos:

Instalar paquetes

PM> Paquete de instalación Microsoft.AspNetCore.WebUtilities
PM> Paquete de instalación Microsoft.AspNetCore.Http.Extensions

Clases importantes

Solo para señalarlos, aquí están las dos clases importantes que usaremos : QueryHelpers , StringValues , QueryBuilder .

El código

// Raw URI including query string with multiple parameters
var rawurl = "https://bencull.com/some/path?key1=val1&key2=val2&key2=valdouble&key3=";

// Parse URI, and grab everything except the query string.
var uri = new Uri(rawurl);
var baseUri = uri.GetComponents(UriComponents.Scheme | UriComponents.Host | UriComponents.Port | UriComponents.Path, UriFormat.UriEscaped);

// Grab just the query string part
var query = QueryHelpers.ParseQuery(uri.Query);

// Convert the StringValues into a list of KeyValue Pairs to make it easier to manipulate
var items = query.SelectMany(x => x.Value, (col, value) => new KeyValuePair<string, string>(col.Key, value)).ToList();

// At this point you can remove items if you want
items.RemoveAll(x => x.Key == "key3"); // Remove all values for key
items.RemoveAll(x => x.Key == "key2" && x.Value == "val2"); // Remove specific value for key

// Use the QueryBuilder to add in new items in a safe way (handles multiples and empty values)
var qb = new QueryBuilder(items);
qb.Add("nonce", "testingnonce");
qb.Add("payerId", "pyr_");

// Reconstruct the original URL with new query string
var fullUri = baseUri + qb.ToQueryString();

Para mantenerse al día con los cambios, puede consultar la publicación de mi blog sobre esto aquí: http://benjii.me/2017/04/parse-modify-query-strings-asp-net-core/

Ben Cull
fuente
17

HttpRequesttiene una Querypropiedad que expone la cadena de consulta analizada a través de la IReadableStringCollectioninterfaz:

/// <summary>
/// Gets the query value collection parsed from owin.RequestQueryString.
/// </summary>
/// <returns>The query value collection parsed from owin.RequestQueryString.</returns>
public abstract IReadableStringCollection Query { get; }

Esta discusión sobre GitHub también lo apunta.

Yuval Itzchakov
fuente
10

Esta función regresa Dictionary<string, string>y no se usa Microsoft.xxxpor compatibilidad

Acepta codificación de parámetros en ambos lados

Acepta claves duplicadas (devuelve el último valor)

var rawurl = "https://emp.com/some/path?key1.name=a%20line%20with%3D&key2=val2&key2=valdouble&key3=&key%204=44#book1";
var uri = new Uri(rawurl);
Dictionary<string, string> queryString = ParseQueryString(uri.Query);

// queryString return:
// key1.name, a line with=
// key2, valdouble
// key3, 
// key 4, 44

public Dictionary<string, string> ParseQueryString(string requestQueryString)
{
    Dictionary<string, string> rc = new Dictionary<string, string>();
    string[] ar1 = requestQueryString.Split(new char[] { '&', '?' });
    foreach (string row in ar1)
    {
        if (string.IsNullOrEmpty(row)) continue;
        int index = row.IndexOf('=');
        if (index < 0) continue;
        rc[Uri.UnescapeDataString(row.Substring(0, index))] = Uri.UnescapeDataString(row.Substring(index + 1)); // use Unescape only parts          
     }
     return rc;
}
Wagner Pereira
fuente
Esto funciona, pero debe agregar la verificación de índice antes de comenzar con la subcadena, ya que existe la posibilidad de que la fila no contenga '='. Lo que causa una excepción.
Taurib
1
Gracias @Taurib por la ayuda, cambiado
Wagner Pereira
1
Advertencia: esto no funcionará si hay matrices en la consulta, ya que el diccionario está configurado <cadena, cadena>. (por ejemplo, "? item = 1 & item = 2") Solución alternativa: use IEnumerable <KeyValuePair <string, string >> o Dictionary <string, StringValues> para .net core 3.1
theCuriousOne
Gracias @theCuriousOne, en esta rutina, devuelve el último valor, "Acepta claves duplicadas (devuelve el último valor)" por simplicidad, tu solución está bien para devolver todos los valores.
Wagner Pereira
1

Es importante tener en cuenta que en el tiempo transcurrido desde que la respuesta principal se marcó como correcta, se Microsoft.AspNetCore.WebUtilitiesha actualizado la versión principal (de 1.xx a 2.xx).

Dicho esto, si está construyendo en contra netcoreapp1.1, deberá ejecutar lo siguiente, que instala la última versión compatible 1.1.2:

Install-Package Microsoft.AspNetCore.WebUtilities -Version 1.1.2

pimbrouwers
fuente
1

Utilizo esto como método de extensión, funciona con cualquier número de parámetros:

public static string AddOrReplaceQueryParameter(this HttpContext c, params string[] nameValues)
    {
        if (nameValues.Length%2!=0)
        {
            throw new Exception("nameValues: has more parameters then values or more values then parameters");
        }
        var qps = new Dictionary<string, StringValues>();
        for (int i = 0; i < nameValues.Length; i+=2)
        {
            qps.Add(nameValues[i], nameValues[i + 1]);
        }
        return c.AddOrReplaceQueryParameters(qps);
    }

public static string AddOrReplaceQueryParameters(this HttpContext c, Dictionary<string,StringValues> pvs)
    {
        var request = c.Request;
        UriBuilder uriBuilder = new UriBuilder
        {
            Scheme = request.Scheme,
            Host = request.Host.Host,
            Port = request.Host.Port ?? 0,
            Path = request.Path.ToString(),
            Query = request.QueryString.ToString()
        };

        var queryParams = QueryHelpers.ParseQuery(uriBuilder.Query);

        foreach (var (p,v) in pvs)
        {
            queryParams.Remove(p);
            queryParams.Add(p, v);
        }

        uriBuilder.Query = "";
        var allQPs = queryParams.ToDictionary(k => k.Key, k => k.Value.ToString());
        var url = QueryHelpers.AddQueryString(uriBuilder.ToString(),allQPs);

        return url;
    }

Enlaces siguiente y anterior, por ejemplo, en una vista:

var next = Context.Request.HttpContext.AddOrReplaceQueryParameter("page",Model.PageIndex+1+"");

var prev = Context.Request.HttpContext.AddOrReplaceQueryParameter("page",Model.PageIndex-1+"");
Gabriel Luca
fuente