If you’ve been using Minimal APIs for a while, you’ve probably run into this pattern:

app.MapGet("/search", (
    [FromQuery] string q,
    [FromQuery] int page,
    [FromHeader(Name = "X-Custom-Header")] string customHeader,
    [FromServices] IHelloService service
) =>
{
    var results = service.SayHello(q);
    return Results.Ok(new
    {
        Query = q,
        Page = page,
        CustomHeader = customHeader,
        Results = results
    });
});

It works perfectly fine until your endpoint starts to grow.
The moment you add more parameters (headers, query params, services, etc.), the signature quickly becomes huge and hard to read/maintain.

With .NET 9, there’s a new attribute that makes this much nicer, AsParameters.

What [AsParameters] does

The [AsParameters] attribute lets you group multiple bound parameters into a single object, while still allowing the framework to handle model binding for you.

You define a record (or record struct) that contains the parameters you’d normally bind individually, annotate each property with the corresponding binding attributes (FromQuery, FromHeader, FromServices, etc.), and then use that type as a single parameter with AsParameters.

Example

Let’s say you want to search for something and need to pass a query string, a page number, a custom header, and also inject a service.

Here’s how that looks with AsParameters:

using Microsoft.AspNetCore.Mvc;

namespace AsParametersExample;

public readonly record struct SearchParameters
{
    [FromQuery(Name = "q")]
    public string Query { get; init; }

    [FromQuery(Name = "page")]
    public int Page { get; init; }

    [FromHeader(Name = "X-Custom-Header")]
    public string CustomHeader { get; init; }

    [FromServices]
    public IHelloService Service { get; init; }
}

app.MapGet("/parameters", ([AsParameters] SearchParameters parameters) =>
{
    var results = parameters.Service.SayHello(parameters.Query);
    return Results.Ok(new
    {
        Query = parameters.Query,
        Page = parameters.Page,
        CustomHeader = parameters.CustomHeader,
        Results = results
    });
});

That’s it, no need for a long parameter list AND you still get full access to all the standard model binding features.

Why it’s better

  1. Cleaner signatures – no more unreadable lists of 10+ bound parameters.
  2. Encapsulation – group related parameters logically (e.g., pagination, filtering, sorting).
  3. Reusability – use the same record across multiple endpoints.
  4. Testability – you can create a SearchParameters instance in tests without dealing with query strings or headers.

Wrapping up

This attribute is one of those small additions that make Minimal APIs feel more natural. In the example I used a readonly record struct with get; init;. If you want to avoid extra heap allocations, do it this way 🙂

Here’s the full Microsoft documentation:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/parameter-binding

Affiliate promo

If you love learning new stuff and want to support me, consider buying a course from Dometrain using this link: Browse courses – Dometrain. Thank you!

Leave a comment

Trending