Route Values and the Query String

Values reach an action from the route (/products/7) or the query string (?page=2). Choose which with the address's meaning, give optional values defaults, and use [FromRoute] and [FromQuery] to be explicit.

Journeyman · ASP.NET Core · C# · Updated

After this lesson you can

  • Read values from the route and the query string, with defaults for optional ones
  • Choose between a route value and a query string value for a piece of information

Key takeaways

  • A route value such as {id:int} fills the parameter with the same name and is required unless marked with ?; other parameters come from the query string.
  • Give a query string parameter a default, such as int page = 1, and it's optional.
  • [FromQuery(Name = "q")] reads ?q=sauce into a parameter with a clearer C# name; [FromRoute] and [FromForm] name the other sources.
  • If leaving a value out makes no sense, it belongs in the route (/products/7); if it means "the default", it belongs in the query string (?page=2).

An action's parameters are filled from the request. Two parts of the address carry values:

/products/7               the route:        id = 7
/products?page=2          the query string: page = 2
/products/search?q=tofu   the query string: q = tofu

From the route

A value in the route template, such as {id:int}, fills the parameter with the same name:

[HttpGet("{id:int}")]
public IActionResult Details(int id)

/products/7 calls Details(7). The route can't match without it, so a route value is required (unless it's marked optional with ?).

From the query string

Any other parameter is looked for in the query string, the part after ?. Give it a default value, and it's optional:

[HttpGet("")]
public IActionResult Index(int page = 1)
{
    int pageCount = (productDao.Count() + PageSize - 1) / PageSize;
    if (page < 1 || page > pageCount)
    {
        return NotFound();
    }

    ViewData["Page"] = page;
    ViewData["PageCount"] = pageCount;
    return View(productDao.GetPage(page, PageSize));
}
Address page
/products 1, the default
/products?page=2 2
/products?page=8 8, which is past the last of 7 pages, so Not Found
/products?page=abc 1: abc isn't an int, so the default stays, and ModelState records the problem

Tag helpers write query strings for you: asp-action="Index" asp-route-page="2" gives href="/products?page=2", because page isn't part of the route.

Saying where a value comes from

Usually the name is enough. When you want to be explicit, or the name in the address differs from the parameter's, use [FromRoute] and [FromQuery]:

[HttpGet("search")]
public IActionResult Search([FromQuery(Name = "q")] string? text)

Now /products/search?q=sauce fills text with sauce. A short q is the usual name for a search box; the parameter keeps its clear C# name. And ?text=sauce fills nothing: with [FromQuery(Name = "q")], only q counts.

Attribute Takes the value from
[FromRoute] The route template, such as {id}
[FromQuery] The query string
[FromForm] A posted form's fields
(none) Wherever binding finds the name: form, route, then query string

Route or query string?

Use the route for Use the query string for
Which thing the page is about: /products/7, /customers/ALFKI How to show it: ?page=2, ?sort=price, ?q=sauce
Values the page can't do without Optional values, with defaults
Addresses people bookmark and share as "the page for X" Filters and settings that change the same page's view

A rule of thumb: if leaving the value out makes no sense, it belongs in the route; if leaving it out means "the default", it belongs in the query string.

Check yourself

2 questions to try on the lesson page.

So what

The route says what the page is about; the query string says how to show it. Choose by meaning, give optional values defaults, and your addresses explain themselves.

Key terms

Cheat sheets