95

Let's say I have some url that looks like this: www.myhost.com/mypage?color=blue

In Asp.Net Core, I'd expect to get the color query parameter value by doing the following:

string color = Request.Query["color"];

But it turns out that the Request.Query["color"] returns a value of type StringValues rather than string. Why is that?

Apparently the StringValues type can hold an array of strings and includes support for implicit conversion to string[] which is cool but why is that needed for a query param value?

Having to get the value like this seems odd:

string color = Request.Query["color"].ToString();

And worse, checking for a value to see if a query param is specified can no longer be done like so

  if(Request.Query["color"] == null) { 
      //param was not specified
  }

but instead must be checked like so

 if(Request.Query["color"].Count == 0) { 
      //param was not specified
 }

Since a single query parameter can't have multiple values (as far as I know) why does Request.Query["color"] return a StringValues object rather than a string?

RonC
  • 31,330
  • 19
  • 94
  • 139
  • 7
    What would you expect the value to be for www.myhost.com/mypage?color=blue&color=red&color=green – podiluska Jan 10 '18 at 13:52
  • 1
    @podiluska I didn't know it was valid to specify a parameter multiple times. If I had done so I'd have expected to just receive the last one i.e. green in your example. – RonC Jan 10 '18 at 13:54
  • Don’t use `Request.Query`, use [model binding](https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding) instead. – poke Jan 10 '18 at 13:56
  • 1
    @RonC Im pretty sure thats the way querystrings *used* to work. But the current "wisdom" in providing collections on the querystring is to repeat the key. Whether you and I agree with that is pretty irrelevant :) – Jamiec Jan 10 '18 at 13:56
  • @Jamiec Gotcha. :-) – RonC Jan 10 '18 at 14:00
  • 2
    you can use `var color = Request.Query["color"]; if (StringValues.IsNullOrEmpty(color)) { // do something };` – Sharif Feb 13 '18 at 20:58

5 Answers5

81

As already mentioned by others, the type is a StringValues object because technically, multiple values are allowed. While the common practice is to just set a single value, the URI specification does not disallow setting values multiple times. And it’s up to the application to decide how to handle that.

That being said, StringValues has an implicit conversion to string, so you don’t actually need to call ToString() on it, you can just use it as if it was a string. So doing things like Request.Query["color"] == "red", or passing it to a method that expects a string will just work.

And worse, checking for a value to see if a query param is specified can no longer be done like so Request.Query["color"] == null but instead must be checked like so Request.Query["color"].Count == 0

That’s only half true. Yes, in order to check whether a StringValues object is empty, you can check its Count property. You can also check against StringValues.Empty:

Request.Query["color"] == StringValues.Empty

However, the initial “issue” is that Request.Query[x] will always return a non-null StringValues object (so it’s safe to check for any value). If you want to check whether a key exists in the query arguments, you should use ContainsKey:

if (Request.Query.ContainsKey("color"))
{
    // only now actually retrieve the value
    string colorValue = Request.Query["color"];
}

Or alternatively, use TryGetValue:

if (Request.Query.TryGetValue("color", out var colorValue))
{
    DoSomething(colorValue);
}

That all being said, accessing Request.Query is not really necessary most of the times. You should just use make use of model binding instead which will automatically give you the query arguments you need by just having them in the action’s signature:

public ActionResult MyAction(string color)
{
    DoSomething(color);
}
poke
  • 369,085
  • 72
  • 557
  • 602
  • thanks for pointing out that `StringValues` does support implicit conversion to `string` so that `String color= Request.Query["color"];` does in fact work. I thought I had seen otherwise, my bad. – RonC Jan 10 '18 at 14:21
  • 2
    This is one of those times I wish I could transfer the OP's accepted mark to a different answer. While mine technically "answers" the question this should have received the recognition of being the right answer. Especially the last bit about not calling `Query` directly, but using model binding. – Jamiec Jan 10 '18 at 16:35
  • I would recommend using `StringValues.IsNullOrEmpty()` instead of `Count == 0`. You'll need to add a `using Microsoft.Extensions.Primitives` – Steve Jul 09 '19 at 20:40
  • @Steve In my answer, I am actually arguing *against* checking the _value_ but checking whether the key exists instead. That being said, `StringValues.IsNullOrEmpty` does have slightly different semantics compared to a check against Empty or checking the count. – poke Jul 10 '19 at 01:23
  • 1
    @Jamiec Sorry it took me so long to notice your comment about changing the accepted mark. I agree and have changed it. I didn't initially because your answer was the first one that helped me understand the situation but I agree with you that this answer is the more robust one. – RonC Jul 27 '20 at 15:08
  • From what I have seen, model binding works great if the values being passed are required and not dynamic. If the values being passed are dynamic and/or not always required, query string is best. Currently I have a set of reports where I use both model binding and query string together. I use model binding to pass the Id of the report (always required), but the data parameters are different and varying in number every time (dynamic), so I use a query string to pass the report data parameters. Breaking it up this way makes the parsing a little easier for me and the code easier to read. – Tyson Gibby Jan 06 '22 at 19:43
66

Because your query can look like this:

www.myhost.com/mypage?color=blue&color=red&color=yellow

And you get all those color values from the one Request.Query["color"] parameter

Jamiec
  • 133,658
  • 13
  • 134
  • 193
  • 3
    thanks, I was unaware that it was valid to specify the same query parameter multiple times in a query string. Given how often it's used on the web (Never seen it before and been doing web development since 1995) I wish it wasn't allowed given that it makes the code less elegant for a seldom used edge case. But now I understand why 'StringValues' was introduced. Interestingly the concept of `StringValues` wasn't introduced until Asp.Net Core, prior versions of Asp.Net returned string. – RonC Jan 10 '18 at 13:58
  • I wonder why the QueryHelpers.AddQueryString in .NET Core is: QueryHelpers.AddQueryString(string uri, IDictionary queryString) – wilsotc Aug 25 '20 at 00:00
9

Just posting for curious souls and probably little do with question. Just cautionary note.

I found myself in similar issue. There are couple other issues with this type.

  1. If you have query parameter with no value. For example: /products?pageNo=1&pageSize=

    You will find yourself getting an exception thrown for pageSize parameter as Count property on StringValues will give you value 1, but underlying _value is "" (empty string) and _values is null. Note - Exception happens you are trying to convert or access values from IQueryCollection)

  2. Using TryGetValue will get you value safely out of StringValues but if it is null (like in case of pageSize parameter above), You will have hard time figuring out why can't you convert StringValues to simple String or why can not compare with null to do further operations on it like validations etc.

  3. To do any checking on StringValues type, use methods provided by the type.

To check for null or empty use - StringValues.IsNullOrEmpty(StringValues value)

Community
  • 1
  • 1
Ameya
  • 726
  • 6
  • 9
  • 1
    Your response is very much related to the question. It's interesting that `StringValue.IsNullOrEmpty(StringValue value)` works. As I mentioned, I ended up checking for null by using `Request.Query["color"].Count == 0` but your approach reads better I think. – RonC Oct 03 '19 at 12:56
0

If you want the first string and are not expecting multiple values just do:

Request.Query["color"][0]
Tono Nam
  • 34,064
  • 78
  • 298
  • 470
-3

Request.Query["color"] returns StringValues because collection of string values can be passed. So, I would suggest you to handle this as action parameter, model binding.

public ActionResult SaveColor([FromUri] string[] color);
lucky
  • 12,734
  • 4
  • 24
  • 46
  • What would this code return if the url were `www.myhost.com/mypage?color=blue&color=red&color=yellow` as @Jamiec and @podiluska mentioned might be the case. – RonC Jan 10 '18 at 14:02
  • 1
    If you want to expect list of strings change the parameter as "string[]". – lucky Jan 10 '18 at 14:05