Routing

This section documents ASP.NET Core on .NET 10 (LTS), the current release — the minimal hosting model, the middleware pipeline, dependency injection, Minimal APIs, MVC & Razor Pages, Blazor with the current render modes, SignalR and gRPC, EF Core, ASP.NET Core Identity and policy-based authorization, output caching, rate limiting, and Native-AOT-aware building — as described by the official documentation at Microsoft Learn, which is the reference these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. .NET ships a major release every November and its APIs continue to evolve: the examples here target .NET 10 / C# 14.

This section’s bibliography lists the reference material consulted while preparing these pages.

Routing maps an incoming request to an endpoint — a delegate plus metadata. It runs in two steps: UseRouting selects the endpoint, and the endpoint middleware at the end of the pipeline executes it.

The two-step model

var app = builder.Build();

app.UseRouting();          // 1. match: sets HttpContext.GetEndpoint()
app.UseAuthentication();
app.UseAuthorization();    // can read the selected endpoint's metadata (e.g. [Authorize])

app.MapGet("/ping", () => "pong");   // 2. execute: the endpoint middleware runs the match
app.Run();

In the minimal hosting model UseRouting and the terminal endpoint middleware are added automatically if you do not add them yourself; call UseRouting explicitly only when you need middleware between matching and execution. All registered endpoints live in an EndpointDataSource. See Routing in ASP.NET Core.

Route templates

A template is literal text plus {parameter} segments:

Syntax Meaning

products/{id}

required parameter id

products/\{id?}

optional parameter

products/\{id=1}

parameter with a default value

files/\{*path}

catch-all (greedy); \{**path} also un-escapes slashes

products/\{id:int}

route constraint — must parse as int

users/\{name:minlength(3)}

constrained length

orders/\{code:regex(…​)}

regex constraint (pattern in parentheses)

app.MapGet("/products/{id:int}", (int id) => $"product {id}");
app.MapGet("/blog/{*slug}", (string slug) => $"post: {slug}");

Built-in constraints include int, long, bool, guid, datetime, alpha, length(n), min(n)/max(n), range(a,b), and regex(…​). Register a custom IRouteConstraint in RouteOptions.ConstraintMap.

Mapping endpoints

app.MapGet("/health", () => Results.Ok());
app.MapPost("/orders", (CreateOrder c) => Results.Created());

app.MapControllers();                                  // attribute-routed controllers
app.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");  // conventional
app.MapRazorPages();
app.MapHub<ChatHub>("/hubs/chat");
app.MapGrpcService<GreeterService>();

Route groups share a prefix and metadata:

var admin = app.MapGroup("/admin").RequireAuthorization("AdminOnly").WithTags("Admin");
admin.MapGet("/users", ListUsers);
admin.MapDelete("/users/{id:guid}", DeleteUser);

Attribute vs. conventional routing

Controllers can be routed by attributes on the class/action or by a global convention. Attribute routing is the default for Web APIs; conventional routing suits classic MVC sites.

[ApiController]
[Route("api/[controller]")]                 // -> api/orders
public sealed class OrdersController : ControllerBase
{
    [HttpGet("{id:int}")]                   // -> GET api/orders/42
    public ActionResult<Order> Get(int id) => ...;

    [HttpGet("recent")]                     // -> GET api/orders/recent
    public IEnumerable<Order> Recent() => ...;
}

When two routes could match, the more specific template wins; break remaining ties with Order on the attribute or [HttpGet("…​", Order = 2)]. See Routing to controller actions.

URL generation and short-circuiting

app.MapGet("/build-link", (LinkGenerator links, HttpContext ctx) =>
    links.GetUriByName(ctx, "GetOrder", new { id = 42 }));

app.MapGet("/orders/{id:int}", (int id) => id).WithName("GetOrder");

A short-circuit route responds during matching and skips the rest of the pipeline (auth, other middleware) — useful for robots.txt or health probes:

app.MapGet("/robots.txt", () => "User-agent: *\nDisallow:").ShortCircuit();
app.MapShortCircuit(404, "favicon.ico", "*.gif");

Matching an incoming URL

flowchart TD U["Incoming URL: GET /products/42"] --> M{"Template match?
/products/{id:int}"} M -- no --> N["Try next endpoint"] M -- yes --> C{"Constraints pass?
id parses as int"} C -- no --> N C -- yes --> S["Endpoint selected
metadata attached"] S --> A["Auth / other middleware
read metadata"] A --> X["Endpoint middleware executes the delegate"]