147

I am given an absolute URI that contains a query string. I'm looking to safely append a value to the query string, and change an existing parameter.

I would prefer not to tack on &foo=bar, or use regular expressions, URI escaping is tricky. Rather I want to use a built-in mechanism that I know will do this correctly and handle the escaping.

I've found a ton of answers that all use HttpUtility. However this being ASP.NET Core, there is no more System.Web assembly anymore, thus no more HttpUtility.

What is the appropriate way to do this in ASP.NET Core while targeting the core runtime?

Community
  • 1
  • 1
vcsjones
  • 138,677
  • 31
  • 291
  • 286
  • An alternative to `Microsoft.AspNet.WebUtilties` may be the [`Mono.HttpUtility` library](https://www.nuget.org/packages/Mono.HttpUtility). – mason May 01 '15 at 18:56
  • 3
    Update 2017: .NET Core 2.0 now includes `HttpUtility` and `ParseQueryString` method. – KTCO Sep 17 '17 at 18:08

7 Answers7

188

If you are using ASP.NET Core 1 or 2, you can do this with Microsoft.AspNetCore.WebUtilities.QueryHelpers in the Microsoft.AspNetCore.WebUtilities package.

If you are using ASP.NET Core 3.0 or greater, WebUtilities is now part of the ASP.NET SDK and does not require a separate nuget package reference.

To parse it into a dictionary:

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

Note that unlike ParseQueryString in System.Web, this returns a dictionary of type IDictionary<string, string[]> in ASP.NET Core 1.x, or IDictionary<string, StringValues> in ASP.NET Core 2.x or greater, so the value is a collection of strings. This is how the dictionary handles multiple query string parameters with the same name.

If you want to add a parameter on to the query string, you can use another method on 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);

Using .net core 2.2 you can get the query string using

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

You will get a collection of key:value pairs - like this

[0] {[companyName, ]}
[1] {[shop, ]}
[2] {[breath, ]}
[3] {[hand, ]}
[4] {[eye, ]}
[5] {[firstAid, ]}
[6] {[eyeCleaner, ]}
Mohamed Badr
  • 2,562
  • 2
  • 26
  • 43
vcsjones
  • 138,677
  • 31
  • 291
  • 286
  • 1
    FYI, the WebUtilities package is not compatible with .net core 1.0. You may be need `Microsoft.AspNetCore.WebUtilities` instead. – Jaime Jul 01 '16 at 09:15
  • 6
    @Jaime Awesome observation! Can you edit my answer with that update so you get credit for it? – vcsjones Jul 01 '16 at 13:57
  • 3
    Edition done. Keeping also the old namespace for the case of legacy .net versions. – Jaime Jul 01 '16 at 20:38
  • 1
    It appears that using QueryHelpers.AddQueryString will automatically UrlEscape the strings - handy. – Josh Jul 07 '16 at 04:29
  • 2
    The return type is now IDictionary rather than IDictionary – btlog Feb 17 '18 at 23:50
  • 1
    Update: WebUtilities is now a part of ASPNET Core 2.1. There is no need to add the Nuget package anymore. – blaz Jan 02 '19 at 05:22
  • 3
    In Asp.net Core 2.2 this doesn't work. ParseQuery returns a Dictionary and there is no AddQueryString that accepts that. – Volker Jun 26 '19 at 12:41
  • 1
    WHY do I need al this code just to read a querystring param? – Edward Oct 01 '20 at 15:31
  • In ASP.NET Core 3 the AddQueryString method doesnt even modify the url. – liqSTAR Mar 11 '21 at 08:02
48

The easiest and most intuitive way to take an absolute URI and manipulate it's query string using ASP.NET Core packages only, can be done in a few easy steps:

Install Packages

PM> Install-Package Microsoft.AspNetCore.WebUtilities
PM> Install-Package Microsoft.AspNetCore.Http.Extensions

Important Classes

Just to point them out, here are the two important classes we'll be using: QueryHelpers, StringValues, QueryBuilder.

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

To keep up to date with any changes, you can check out my blog post about this here: http://benjii.me/2017/04/parse-modify-query-strings-asp-net-core/

Ben Cull
  • 9,434
  • 7
  • 43
  • 38
22

HttpRequest has a Query property which exposes the parsed query string via the IReadableStringCollection interface:

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

This discussion on GitHub points to it as well.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
15

This function return Dictionary<string, string> and does not use Microsoft.xxx for compatibility

Accepts parameter encoding in both sides

Accepts duplicate keys (return last value)

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
  • 1,050
  • 11
  • 11
  • This works, but you should add index check before starting to substring, since there is possibility, that row doesn't contain '='. Which causes exception. – Taurib Apr 19 '18 at 18:38
  • 1
    Thanks @Taurib for the help, changed – Wagner Pereira Apr 27 '18 at 14:33
  • 1
    Warning: this will not work if there are arrays in the query since the dictionary is set ! (for example "?item=1&item=2") Workaraound: use IEnumerable> or Dictionary for .net core 3.1 – theCuriousOne Jun 16 '20 at 09:04
  • Thanks @theCuriousOne , in this routine, return last value, "Accepts duplicate keys (return last value)" for simplicity, your solution is ok to return all values. – Wagner Pereira Jun 18 '20 at 19:19
4

It's important to note that in the time since the top answer has been flagged as correct that Microsoft.AspNetCore.WebUtilities has had a major version update (from 1.x.x to 2.x.x).

That said, if you're building against netcoreapp1.1 you will need to run the following, which installs the latest supported version 1.1.2:

Install-Package Microsoft.AspNetCore.WebUtilities -Version 1.1.2

pim
  • 12,019
  • 6
  • 66
  • 69
2

I'm not sure at what point it was added but as early as .NET Core 3.1 HttpUtility.ParseQueryString is available and built into the standard .NET Microsoft.NETCore.App framework. I was able to access it from a class library with no additional NuGet packages or special dll references required.

I've tested it with .NET Core 3.1, .NET 6, and .NET 7.

The benefit of this approach is that you do not have to reference any web libraries which will add bloat to your project if you are building a library that may be used outside of the context of ASP.NET.

... of course, if you are needing this within an ASP.NET Core app, the Microsoft.AspNetCore.WebUtilities class suggested by others is perfectly valid.

EDIT: @KTCO pointed out in his comment on the OP back in 2017 that this class was available in .NET Core 2.0 as well.

CrossBound
  • 56
  • 5
  • 1
    Thanks for the slightly more up-to-date solution. I am using the more modern `Microsoft.NET.Sdk` SDK, and had to reference the `System.Web` assembly as well though. – Zero3 Dec 22 '22 at 14:04
  • 1
    @Zero3 You are correct that you have to add a using statement at the top of your file to import the namespace, but you should not have to reference any additional nuget packages. – CrossBound Jan 05 '23 at 20:20
  • I also had to reference the assembly in my project file. – Zero3 Jan 10 '23 at 09:26
1

I use this as extention method, works with any number of params:

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

Next and prev links for example in a view:

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

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