Analyser et modifier une chaîne de requête dans .NET Core

113

On me donne un URI absolu qui contient une chaîne de requête. Je cherche à ajouter en toute sécurité une valeur à la chaîne de requête et à modifier un paramètre existant.

Je préférerais ne pas clouer &foo=barou utiliser des expressions régulières, l'échappement d'URI est délicat. Je souhaite plutôt utiliser un mécanisme intégré dont je sais qu'il le fera correctement et gérera l'échappement.

J'ai trouvé une tonne de réponses que toutes utilisent HttpUtility. Cependant, étant ASP.NET Core, il n'y a plus d'assembly System.Web, donc plus HttpUtility.

Quelle est la manière appropriée de le faire dans ASP.NET Core tout en ciblant le runtime principal?

vcsjones
la source
Une alternative à Microsoft.AspNet.WebUtiltiespeut-être la Mono.HttpUtilitybibliothèque .
mason
J'ai écrit un post pour le même, jetez un œil ici: neelbhatt40.wordpress.com/2017/07/06/…
Neel
2
Mise à jour 2017: .NET Core 2.0 inclut maintenant HttpUtilityet ParseQueryStringméthode.
KTCO le

Réponses:

152

Si vous utilisez ASP.NET Core 1 ou 2, vous pouvez le faire avec Microsoft.AspNetCore.WebUtilities.QueryHelpersdans le package Microsoft.AspNetCore.WebUtilities .

Si vous utilisez ASP.NET Core 3.0 ou version ultérieure, WebUtilitiesfait maintenant partie du SDK ASP.NET et ne nécessite pas de référence de package nuget distincte.

Pour l'analyser dans un dictionnaire:

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

Notez que contrairement ParseQueryStringà System.Web, cela renvoie un dictionnaire de type IDictionary<string, string[]>dans ASP.NET Core 1.x, ou IDictionary<string, StringValues>dans ASP.NET Core 2.x ou version ultérieure, la valeur est donc une collection de chaînes. C'est ainsi que le dictionnaire gère plusieurs paramètres de chaîne de requête avec le même nom.

Si vous souhaitez ajouter un paramètre à la chaîne de requête, vous pouvez utiliser une autre méthode sur 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);

En utilisant .net core 2.2, vous pouvez obtenir la chaîne de requête en utilisant

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

Vous obtiendrez une collection de paires clé: valeur - comme ceci

[0] {[companyName, ]}
[1] {[shop, ]}
[2] {[breath, ]}
[3] {[hand, ]}
[4] {[eye, ]}
[5] {[firstAid, ]}
[6] {[eyeCleaner, ]}
vcsjones
la source
1
Pour info, le package WebUtilities n'est pas compatible avec .net core 1.0. Vous en aurez peut-être besoin à la Microsoft.AspNetCore.WebUtilitiesplace.
Jaime
6
@Jaime Superbe observation! Pouvez-vous modifier ma réponse avec cette mise à jour afin d'en obtenir le crédit?
vcsjones
3
Edition terminée. Conserver également l'ancien espace de noms pour le cas des anciennes versions .net.
Jaime
1
Il semble que l'utilisation de QueryHelpers.AddQueryString va automatiquement UrlEscape les chaînes - pratique.
Josh
2
Le type de retour est désormais IDictionary <string, StringValues> plutôt que IDictionary <string, string []>
btlog
35

Le moyen le plus simple et le plus intuitif de prendre un URI absolu et de manipuler sa chaîne de requête à l'aide de packages ASP.NET Core uniquement, peut être effectué en quelques étapes simples:

Installer des packages

PM> Package d'installation Microsoft.AspNetCore.WebUtilities
PM> Package d'installation Microsoft.AspNetCore.Http.Extensions

Classes importantes

Pour les signaler, voici les deux classes importantes que nous utiliserons: QueryHelpers , StringValues , QueryBuilder .

Le code

// 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();

Pour vous tenir au courant de tout changement, vous pouvez consulter mon article de blog à ce sujet ici: http://benjii.me/2017/04/parse-modify-query-strings-asp-net-core/

Ben Cull
la source
17

HttpRequesta une Querypropriété qui expose la chaîne de requête analysée via l' 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; }

Cette discussion sur GitHub le souligne également.

Yuval Itzchakov
la source
10

Cette fonction retourne Dictionary<string, string>et ne l'utilise pas Microsoft.xxxpour la compatibilité

Accepte le codage des paramètres des deux côtés

Accepte les clés en double (retourne la dernière valeur)

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
la source
Cela fonctionne, mais vous devez ajouter une vérification d'index avant de commencer à sous-chaîne, car il est possible que cette ligne ne contienne pas '='. Ce qui provoque une exception.
Taurib
1
Merci @Taurib pour l'aide, changé
Wagner Pereira
1
Attention: cela ne fonctionnera pas s'il y a des tableaux dans la requête puisque le dictionnaire est défini <string, string>! (par exemple "? item = 1 & item = 2") Solution de contournement: utilisez IEnumerable <KeyValuePair <string, string >> ou Dictionary <string, StringValues> pour .net core 3.1
theCuriousOne
Merci @theCuriousOne, dans cette routine, retourne la dernière valeur, "Accepte les clés en double (retourne la dernière valeur)" pour plus de simplicité, votre solution est correcte pour renvoyer toutes les valeurs.
Wagner Pereira
1

Il est important de noter que depuis que la première réponse a été signalée comme correcte, Microsoft.AspNetCore.WebUtilitiesune mise à jour majeure de la version a été effectuée (de 1.xx à 2.xx).

Cela dit, si vous construisez contre, netcoreapp1.1vous devrez exécuter ce qui suit, qui installe la dernière version prise en charge 1.1.2:

Install-Package Microsoft.AspNetCore.WebUtilities -Version 1.1.2

pimbrouwers
la source
1

J'utilise ceci comme méthode d'extension, fonctionne avec n'importe quel nombre de paramètres:

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;
    }

Liens suivant et précédent par exemple dans une vue:

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

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