Analise e modifique uma string de consulta no .NET Core

113

Recebo um URI absoluto que contém uma string de consulta. Estou procurando acrescentar com segurança um valor à string de consulta e alterar um parâmetro existente.

Eu preferiria não insistir &foo=barou usar expressões regulares, porque o escape de URI é complicado. Em vez disso, quero usar um mecanismo integrado que sei que fará isso corretamente e tratará do escape.

Eu encontrei uma tonelada de respostas que todos usam HttpUtility. No entanto, sendo este ASP.NET Core, não há mais assembly System.Web, portanto, não há mais HttpUtility.

Qual é a maneira apropriada de fazer isso no ASP.NET Core enquanto direciona o tempo de execução do núcleo?

vcsjones
fonte
Uma alternativa Microsoft.AspNet.WebUtiltiespode ser a Mono.HttpUtilitybiblioteca .
pedreiro
Eu escrevi um post sobre o mesmo, dê uma olhada aqui: neelbhatt40.wordpress.com/2017/07/06/…
Neel
2
Atualização de 2017: .NET Núcleo 2.0 inclui agora HttpUtilitye ParseQueryStringmétodo.
KTCO

Respostas:

152

Se estiver usando o ASP.NET Core 1 ou 2, você pode fazer isso com Microsoft.AspNetCore.WebUtilities.QueryHelperso pacote Microsoft.AspNetCore.WebUtilities .

Se você estiver usando o ASP.NET Core 3.0 ou superior, WebUtilitiesagora faz parte do SDK do ASP.NET e não requer uma referência de pacote nuget separada.

Para analisá-lo em um dicionário:

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

Observe que, ao contrário ParseQueryStringde System.Web, isso retorna um dicionário do tipo IDictionary<string, string[]>no ASP.NET Core 1.x ou IDictionary<string, StringValues>no ASP.NET Core 2.x ou superior, portanto, o valor é uma coleção de strings. É assim que o dicionário lida com vários parâmetros de string de consulta com o mesmo nome.

Se quiser adicionar um parâmetro à string de consulta, você pode usar outro método em 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, você pode obter a string de consulta usando

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

Você obterá uma coleção de pares chave: valor - como este

[0] {[companyName, ]}
[1] {[shop, ]}
[2] {[breath, ]}
[3] {[hand, ]}
[4] {[eye, ]}
[5] {[firstAid, ]}
[6] {[eyeCleaner, ]}
vcsjones
fonte
1
FYI, o pacote WebUtilities não é compatível com .net core 1.0. Você pode ser necessário em Microsoft.AspNetCore.WebUtilitiesvez disso.
Jaime
6
@Jaime Observação incrível! Você pode editar minha resposta com essa atualização para receber crédito por ela?
vcsjones 01 de
3
Edição concluída. Mantendo também o antigo namespace para o caso de versões legadas .net.
Jaime
1
Parece que usar QueryHelpers.AddQueryString irá automaticamente usar o UrlEscape das strings - útil.
Josh
2
O tipo de retorno agora é IDictionary <string, StringValues> em vez de IDictionary <string, string []>
btlog
35

A maneira mais fácil e intuitiva de pegar um URI absoluto e manipular sua string de consulta usando apenas pacotes ASP.NET Core pode ser realizada em algumas etapas fáceis:

Pacotes de instalação

PM> Instalar Pacote Microsoft.AspNetCore.WebUtilities
PM> Instalar Pacote Microsoft.AspNetCore.Http.Extensions

Aulas importantes

Apenas para apontá-los, aqui estão as duas classes importantes que usaremos : QueryHelpers , StringValues , QueryBuilder .

O 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 se manter atualizado com as alterações, você pode verificar minha postagem do blog sobre isso aqui: http://benjii.me/2017/04/parse-modify-query-strings-asp-net-core/

Ben Cull
fonte
17

HttpRequesttem uma Querypropriedade que expõe a string de consulta analisada por meio da IReadableStringCollectioninterface:

/// <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 discussão no GitHub também aponta para isso.

Yuval Itzchakov
fonte
10

Esta função retorna Dictionary<string, string>e não usa Microsoft.xxxpara compatibilidade

Aceita codificação de parâmetro em ambos os lados

Aceita chaves duplicadas (retorna o ú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
fonte
Isso funciona, mas você deve adicionar a verificação de índice antes de iniciar a substring, uma vez que existe a possibilidade de que a linha não contenha '='. O que causa exceção.
Taurib de
1
Obrigado @Taurib pela ajuda, alterado
Wagner Pereira
1
Aviso: isso não funcionará se houver arrays na consulta, pois o dicionário está definido como <string, string>! (por exemplo "? item = 1 & item = 2") Solução alternativa: use IEnumerable <KeyValuePair <string, string >> ou Dictionary <string, StringValues> para .net core 3.1
theCuriousOne
Obrigado @theCuriousOne, nesta rotina, retorne o último valor, "Aceita chaves duplicadas (retorna o último valor)" para simplificar, sua solução está ok para retornar todos os valores.
Wagner Pereira
1

É importante observar que desde que a resposta principal foi sinalizada como correta Microsoft.AspNetCore.WebUtilities, houve uma atualização de versão principal (de 1.xx para 2.xx).

Dito isso, se você estiver construindo com base em netcoreapp1.1, precisará executar o seguinte, que instala a versão mais recente compatível 1.1.2:

Install-Package Microsoft.AspNetCore.WebUtilities -Version 1.1.2

pimbrouwers
fonte
1

Eu uso isso como um método de extensão, funciona com qualquer 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;
    }

Links seguinte e anterior, por exemplo em uma visualização:

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

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