[FromQuery]

Bind query-string values to parameters and filter objects, rename keys such as page-size, and handle missing, repeated and malformed values.

Signature
[FromQuery(Name = "page-size")] int pageSize = 20  |  [FromQuery] OrderFilter filter
  • Added in .NET ASP.NET Core 1.0

Parameters

ParameterTypeDescription
$Nameoptionalstring?= null (the parameter name)The query-string key to read when it differs from the parameter name, for example "page-size" or "sort_by". Keys are matched case-insensitively.

Return value

n/aBinds the parameter (or each property of a complex type in MVC) from the query string, converting text with invariant culture.

Errors

Nothing is thrown for a bad value: MVC records a model-state error (400 under [ApiController]); minimal APIs return 400. A minimal API [FromQuery] on a complex type without TryParse fails when the endpoint is built.

Changelog

  • PHP ASP.NET Core 6.0Minimal APIs honour [FromQuery]; simple parameters that are not route values are inferred from the query string.
  • PHP ASP.NET Core 7.0Minimal APIs bind arrays and StringValues from repeated query keys.

Query-string binding is what both MVC and minimal APIs fall back to for simple parameters that are not route values. You add [FromQuery] for three reasons: the key is not a valid C# name (page-size), a complex type in a controller must bind from the query instead of the inferred body, or you want the source to be obvious to the next reader.

Missing, repeated and malformed values

RequestController, int pageMinimal API, int page
?page=333
key missing0, no error400 (required)
?page=abcmodel-state error, 400 under [ApiController]400
?status=paid&status=shipped into string[]both valuesboth values

That first difference catches people moving code between the two models. In a controller a missing value type quietly becomes its default; add a default (int page = 1), make it nullable, or mark it [BindRequired] when absence must be an error. In a minimal API a non-nullable parameter without a default is required.

Culture and formats

Query values are parsed with the invariant culture, unlike form values which use the current culture. ?from=2026-10-01 binds to DateOnly and DateTime predictably; 01/10/2026 means January 10th. Document ISO 8601 for dates and a dot as the decimal separator.

Filter objects

In a controller, [FromQuery] OrderFilter filter binds each public settable property from the key of the same name (or filter.Status). Minimal APIs do not bind complex types from the query; use [AsParameters] on a record whose members carry their own sources.

Production failure modes

  • An unbounded ?pageSize=100000 turns one request into a full table scan. Validate a range or clamp it.
  • Query strings end up in access logs, proxy logs and browser history. Never accept tokens, emails or other personal data as query parameters.
  • Renaming a C# parameter renames the public key unless Name pins it.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Accept ?page=2&page-size=50 on GET /api/orders with defaults when either key is missing
  • Filter orders by several statuses with ?status=paid&status=shipped bound to a string array
  • Reject GET /api/reports/revenue without ?year= instead of producing a report for year 0

Frequently asked questions

Why is my int query parameter 0 instead of an error?

Controllers bind a missing value type to its default and record no error. Use [BindRequired], make it nullable with [Required], or give it a meaningful default.

Can minimal APIs bind a class from the query string?

Not with [FromQuery] on the class. Use [AsParameters] on a record or class whose properties are bound individually, or give the type a static TryParse or BindAsync method.

Are query keys case-sensitive?

No. ?Page=2 and ?page=2 bind the same parameter. Values are case-preserved, so compare them case-insensitively where that matters (status=Paid vs paid).

More examples

Required query values in a controller

<code>[BindRequired]</code> makes absence of the key a model-state error, so <code>[ApiController]</code> answers 400. <code>[Required]</code> would not help on a non-nullable <code>int</code>: the value is never null, so the validator always passes. Use <code>[Required]</code> on <code>int?</code> or <code>[BindRequired]</code> on <code>int</code>.

Example · csharp
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace Shop.Api;

[ApiController]
[Route("api/reports")]
public class ReportsController : ControllerBase
{
    // Without [BindRequired], a missing ?year= silently becomes 0 and the report covers year 0.
    [HttpGet("revenue")]
    public IActionResult Revenue([FromQuery, BindRequired] int year, [FromQuery] int? month) =>
        Ok(new { year, month, revenue = 182_340.75m });
}

Clamp paging in one place

Nullable parameters make every query key optional in a minimal API, and a small value type turns them into safe numbers. Clamping instead of rejecting suits public list endpoints; reject with 400 when a silently smaller page would surprise a client that iterates pages.

Example · csharp
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", ([FromQuery] int? page, [FromQuery(Name = "page-size")] int? pageSize) =>
{
    var paging = Paging.From(page, pageSize);
    return TypedResults.Ok(new { paging.Page, paging.Size, skip = paging.Skip });
});

app.Run();

public readonly record struct Paging(int Page, int Size)
{
    public const int MaxSize = 100;
    public int Skip => (Page - 1) * Size;

    public static Paging From(int? page, int? size) =>
        new(Math.Max(page ?? 1, 1), Math.Clamp(size ?? 20, 1, MaxSize));
}

Repeated keys versus comma-separated values

Array binding uses repeated keys. A comma-separated value arrives as one string, so either document the repeated-key form or accept a single string and split it yourself; mixing both styles across endpoints is a common source of client bugs.

Example · http
@api = http://localhost:5080

### Binds string[] status = ["paid", "shipped"]
GET {{api}}/api/orders?status=paid&status=shipped

### Binds ONE element: "paid,shipped" (no splitting on commas)
GET {{api}}/api/orders?status=paid,shipped

### Keys are case-insensitive: binds pageSize = 50
GET {{api}}/api/orders?PAGE-SIZE=50

Discussion

  • Be the first to comment on this lesson.