[{"data":1,"prerenderedAt":110},["ShallowReactive",2],{"qa-\u002Fdotnet\u002Faspnet-core\u002Frouting":3},{"page":4,"siblings":93,"blog":107},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":12,"path":20,"questions":21,"questionsCount":84,"related":85,"seo":86,"seoDescription":87,"stem":88,"subtopic":6,"topic":89,"topicSlug":90,"updated":91,"__hash__":92},"qa\u002Fdotnet\u002Faspnet-core\u002Frouting.md","Routing",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md",".NET Core","dotnet",{},true,"\u002Fdotnet\u002Faspnet-core\u002Frouting",[22,27,31,35,39,43,47,51,55,59,63,68,72,76,80],{"id":23,"difficulty":24,"q":25,"a":26},"routing-what-is","easy","What is routing in ASP.NET Core and how does endpoint routing work?","**Routing** maps incoming HTTP requests to endpoints (controller actions, Razor Pages,\nor minimal API handlers) based on the URL path, HTTP method, and other metadata.\n**Endpoint routing** (introduced in ASP.NET Core 3.0) separates route *matching*\n(`UseRouting`) from route *execution* (`MapControllers`), allowing middleware in\nbetween to read endpoint metadata before the action runs.\n\n```csharp\nvar builder = WebApplication.CreateBuilder(args);\nbuilder.Services.AddControllers();\n\nvar app = builder.Build();\n\napp.UseRouting();          \u002F\u002F 1. Match the request to an endpoint\napp.UseAuthentication();   \u002F\u002F 2. Run auth AFTER matching (can see endpoint metadata)\napp.UseAuthorization();    \u002F\u002F 3. Enforce authorization policy from endpoint metadata\napp.MapControllers();      \u002F\u002F 4. Execute the matched controller action\n\napp.Run();\n```\n\nThe key advantage of this design: `UseAuthorization` can read the `[Authorize]`\nattribute on the matched endpoint *before* dispatching, so it can reject unauthorized\nrequests without ever entering the controller method.\n\n**Rule of thumb:** Always place `UseRouting` before `UseAuthentication` \u002F\n`UseAuthorization`, and those before `MapControllers`. This is the order that lets\nmiddleware inspect endpoint metadata correctly.\n",{"id":28,"difficulty":24,"q":29,"a":30},"conventional-vs-attribute-routing","What is the difference between conventional routing and attribute routing?","**Conventional routing** defines URL patterns in a central place (`MapControllerRoute`).\n**Attribute routing** places route templates directly on controllers and actions with\n`[Route]`, `[HttpGet]`, `[HttpPost]`, etc.\n\n```csharp\n\u002F\u002F Conventional routing — one pattern matches many controllers:\napp.MapControllerRoute(\n    name: \"default\",\n    pattern: \"{controller=Home}\u002F{action=Index}\u002F{id?}\");\n\u002F\u002F \u002FProducts\u002FDetails\u002F5  → ProductsController.Details(id: 5)\n\u002F\u002F \u002FHome               → HomeController.Index()\n\n\u002F\u002F Attribute routing — template lives on the action:\n[ApiController]\n[Route(\"api\u002F[controller]\")]          \u002F\u002F api\u002Fproducts\npublic class ProductsController : ControllerBase\n{\n    [HttpGet]                        \u002F\u002F GET api\u002Fproducts\n    public IActionResult List() => Ok(_products);\n\n    [HttpGet(\"{id:int}\")]            \u002F\u002F GET api\u002Fproducts\u002F5\n    public IActionResult Get(int id) => Ok(Find(id));\n\n    [HttpPost]                       \u002F\u002F POST api\u002Fproducts\n    public IActionResult Create([FromBody] Product p) { ... }\n\n    [HttpDelete(\"{id:int}\")]         \u002F\u002F DELETE api\u002Fproducts\u002F5\n    public IActionResult Delete(int id) { ... }\n}\n```\n\n| Aspect | Conventional | Attribute |\n|---|---|---|\n| Route definition | `Program.cs` | Decorators on class\u002Fmethod |\n| Best for | MVC views (consistent URL structure) | REST APIs (per-action control) |\n| Override | Attribute wins | N\u002FA — all attribute |\n| Required for `[ApiController]` | No | Yes |\n\n**Rule of thumb:** Use conventional routing for MVC Razor view apps where URLs follow\na predictable pattern. Use attribute routing for Web APIs — `[ApiController]` requires it.\n",{"id":32,"difficulty":14,"q":33,"a":34},"route-constraints","What are route constraints and how do you use them?","**Route constraints** restrict which requests match a route parameter by type,\nformat, or range. They are specified in the route template with a colon (`:`).\n\n```csharp\n\u002F\u002F Built-in constraints:\n[HttpGet(\"{id:int}\")]              \u002F\u002F id must parse as int\n[HttpGet(\"{id:int:min(1)}\")]       \u002F\u002F int ≥ 1\n[HttpGet(\"{slug:alpha}\")]          \u002F\u002F letters only\n[HttpGet(\"{id:guid}\")]             \u002F\u002F GUID format\n[HttpGet(\"{date:datetime}\")]       \u002F\u002F parseable DateTime\n[HttpGet(\"{code:length(5)}\")]      \u002F\u002F exactly 5 characters\n[HttpGet(\"{code:regex(^[A-Z]{{3}}\\\\d{{2}}$)}\")]  \u002F\u002F regex pattern\n\n\u002F\u002F Example — accept only positive integer product ids:\n[HttpGet(\"products\u002F{id:int:min(1)}\")]\npublic IActionResult GetProduct(int id)\n{\n    var product = _repo.Find(id);\n    return product is null ? NotFound() : Ok(product);\n}\n\n\u002F\u002F Minimal API style:\napp.MapGet(\"\u002Forders\u002F{id:int}\", (int id) => FindOrder(id));\n```\n\nCustom constraint:\n\n```csharp\npublic class EvenNumberConstraint : IRouteConstraint\n{\n    public bool Match(HttpContext? httpContext, IRouter? route,\n        string routeKey, RouteValueDictionary values, RouteDirection direction)\n    {\n        return values.TryGetValue(routeKey, out var val)\n            && int.TryParse(val?.ToString(), out int n)\n            && n % 2 == 0;\n    }\n}\n\n\u002F\u002F Register:\nbuilder.Services.Configure\u003CRouteOptions>(o =>\n    o.ConstraintMap.Add(\"even\", typeof(EvenNumberConstraint)));\n\n\u002F\u002F Use:\napp.MapGet(\"\u002Fitems\u002F{id:even}\", (int id) => $\"Even id: {id}\");\n```\n\n**Rule of thumb:** Prefer built-in constraints for type and range validation.\nRoute constraints are for routing — not business validation. Always validate\ninput again in your action method or command handler.\n",{"id":36,"difficulty":24,"q":37,"a":38},"route-templates","How do route templates work — what are literal segments, parameters, and catch-all parameters?","A route template is a pattern string composed of **literal segments**, **route\nparameters** (`{name}`), optional parameters (`{name?}`), default values\n(`{name=default}`), and **catch-all parameters** (`{**rest}` or `{*rest}`).\n\n```csharp\n\u002F\u002F Literal segment — must match exactly:\n[HttpGet(\"api\u002Fv1\u002Fproducts\")]        \u002F\u002F only matches \u002Fapi\u002Fv1\u002Fproducts\n\n\u002F\u002F Route parameter — captures a URL segment:\n[HttpGet(\"products\u002F{id}\")]          \u002F\u002F captures: id = \"42\" for \u002Fproducts\u002F42\n\n\u002F\u002F Optional parameter:\n[HttpGet(\"search\u002F{term?}\")]         \u002F\u002F matches \u002Fsearch and \u002Fsearch\u002Fshoes\npublic IActionResult Search(string? term) { ... }\n\n\u002F\u002F Default value:\n[HttpGet(\"page\u002F{number=1}\")]        \u002F\u002F \u002Fpage → number=1; \u002Fpage\u002F3 → number=3\n\n\u002F\u002F Catch-all — captures the remainder including slashes:\n[HttpGet(\"files\u002F{**path}\")]         \u002F\u002F \u002Ffiles\u002Fa\u002Fb\u002Fc → path = \"a\u002Fb\u002Fc\"\npublic IActionResult GetFile(string path) { ... }\n\n\u002F\u002F Multiple parameters:\n[HttpGet(\"{year:int}\u002F{month:int}\u002F{slug}\")]\n\u002F\u002F \u002F2026\u002F6\u002Fmy-post → year=2026, month=6, slug=\"my-post\"\npublic IActionResult GetPost(int year, int month, string slug) { ... }\n```\n\nToken replacement in attribute routing:\n\n```csharp\n[Route(\"api\u002F[controller]\")]   \u002F\u002F [controller] → \"Products\" (class name minus suffix)\n[Route(\"api\u002F[controller]\u002F[action]\")]  \u002F\u002F [action] → method name\n```\n\n**Rule of thumb:** Use `{name}` for required segments, `{name?}` for optional\nsegments, and `{**rest}` for path-like catch-all values that include `\u002F`.\n",{"id":40,"difficulty":24,"q":41,"a":42},"http-method-attributes","What are the HTTP verb attributes (`[HttpGet]`, `[HttpPost]`, etc.) and how do they map to REST?","The HTTP verb attributes constrain a route to a specific HTTP method and optionally\nadd a path template. They implement the REST convention of using different HTTP\nmethods to represent different operations on the same resource URL.\n\n```csharp\n[ApiController]\n[Route(\"api\u002Forders\")]\npublic class OrdersController : ControllerBase\n{\n    [HttpGet]                          \u002F\u002F GET \u002Fapi\u002Forders — list all\n    public IActionResult List() => Ok(_orders);\n\n    [HttpGet(\"{id:int}\")]              \u002F\u002F GET \u002Fapi\u002Forders\u002F5 — get one\n    public IActionResult Get(int id) => Ok(Find(id));\n\n    [HttpPost]                         \u002F\u002F POST \u002Fapi\u002Forders — create\n    public IActionResult Create([FromBody] CreateOrderDto dto) { ... }\n\n    [HttpPut(\"{id:int}\")]              \u002F\u002F PUT \u002Fapi\u002Forders\u002F5 — full replace\n    public IActionResult Replace(int id, [FromBody] OrderDto dto) { ... }\n\n    [HttpPatch(\"{id:int}\")]            \u002F\u002F PATCH \u002Fapi\u002Forders\u002F5 — partial update\n    public IActionResult Update(int id, [FromBody] JsonPatchDocument\u003COrder> patch) { ... }\n\n    [HttpDelete(\"{id:int}\")]           \u002F\u002F DELETE \u002Fapi\u002Forders\u002F5 — delete\n    public IActionResult Delete(int id) { ... }\n\n    [HttpHead(\"{id:int}\")]             \u002F\u002F HEAD \u002Fapi\u002Forders\u002F5 — check existence\n    public IActionResult Exists(int id) { ... }\n}\n```\n\n`[AcceptVerbs(\"GET\", \"POST\")]` is the multi-verb variant:\n\n```csharp\n[AcceptVerbs(\"GET\", \"POST\")]\npublic IActionResult Search(string? q) { ... }\n```\n\n**Rule of thumb:** Follow REST conventions — GET for reads (idempotent, cacheable),\nPOST for creates, PUT for full updates, PATCH for partial updates, DELETE for\nremoval. Never use GET for actions with side effects.\n",{"id":44,"difficulty":14,"q":45,"a":46},"link-generation","How do you generate URLs in ASP.NET Core using `IUrlHelper` and `LinkGenerator`?","**`IUrlHelper`** is available in controllers and Razor Pages. **`LinkGenerator`**\nis a DI service usable anywhere, including middleware and background services.\n\n```csharp\n\u002F\u002F In a controller — IUrlHelper via Url property:\npublic class OrdersController : ControllerBase\n{\n    [HttpPost]\n    public IActionResult Create([FromBody] CreateOrderDto dto)\n    {\n        var order = _service.Create(dto);\n\n        \u002F\u002F Generate URL for the Get action:\n        var url = Url.Action(\n            action: \"Get\",\n            controller: \"Orders\",\n            values: new { id = order.Id });\n        \u002F\u002F → \u002Fapi\u002Forders\u002F42\n\n        return Created(url, order); \u002F\u002F 201 with Location header\n    }\n}\n\n\u002F\u002F Minimal APIs — LinkGenerator in DI:\napp.MapPost(\"\u002Forders\", (CreateOrderDto dto, LinkGenerator links) =>\n{\n    var order = CreateOrder(dto);\n    var url = links.GetPathByName(\"get-order\", new { id = order.Id });\n    return Results.Created(url, order);\n});\n\napp.MapGet(\"\u002Forders\u002F{id:int}\", (int id) => GetOrder(id))\n   .WithName(\"get-order\"); \u002F\u002F named endpoint for link generation\n```\n\n`Url.RouteUrl` uses the route name; `Url.Action` uses controller\u002Faction names.\n\n```csharp\n\u002F\u002F Using named routes:\n[HttpGet(\"{id:int}\", Name = \"GetOrder\")]\npublic IActionResult Get(int id) => Ok(Find(id));\n\nvar url = Url.RouteUrl(\"GetOrder\", new { id = 5 }); \u002F\u002F → \u002Fapi\u002Forders\u002F5\n```\n\n**Rule of thumb:** Always use `IUrlHelper` or `LinkGenerator` to generate URLs\nrather than hard-coding strings — route templates can change and named routes\nensure your links stay correct.\n",{"id":48,"difficulty":14,"q":49,"a":50},"areas","What are Areas in ASP.NET Core MVC and when do you use them?","**Areas** partition a large MVC application into functional groups, each with its\nown controllers, views, and models. They are useful for multi-module applications\n(e.g., Admin and Public sections with separate `HomeController` classes).\n\n```csharp\n\u002F\u002F Directory structure:\n\u002F\u002F Areas\u002F\n\u002F\u002F Admin\u002F\n\u002F\u002F Controllers\u002FProductsController.cs\n\u002F\u002F Views\u002FProducts\u002FIndex.cshtml\n\u002F\u002F Shop\u002F\n\u002F\u002F Controllers\u002FProductsController.cs\n\u002F\u002F Views\u002FProducts\u002FIndex.cshtml\n\n\u002F\u002F Controller — mark with [Area]:\n[Area(\"Admin\")]\n[Route(\"admin\u002F[controller]\u002F[action]\")]\npublic class ProductsController : Controller\n{\n    public IActionResult Index() => View();\n}\n\n\u002F\u002F Register area routing (in addition to conventional routing):\napp.MapControllerRoute(\n    name: \"areas\",\n    pattern: \"{area:exists}\u002F{controller=Home}\u002F{action=Index}\u002F{id?}\");\n\u002F\u002F \u002Fadmin\u002Fproducts → Admin area, ProductsController.Index\n\u002F\u002F \u002Fshop\u002Fproducts  → Shop area, ProductsController.Index\n```\n\nLink generation across areas requires specifying `area`:\n\n```csharp\n\u002F\u002F In a Razor view inside the Shop area, link to Admin:\n\u003Ca asp-area=\"Admin\" asp-controller=\"Products\" asp-action=\"Index\">\n    Admin Products\n\u003C\u002Fa>\n```\n\n**Rule of thumb:** Use Areas when a single project needs distinct sections with\noverlapping controller or view names (Admin, API, Shop). For true separation, prefer\nseparate projects. Areas add complexity — avoid them for small apps.\n",{"id":52,"difficulty":14,"q":53,"a":54},"minimal-apis-vs-controllers","What are minimal APIs and how do they compare to controller-based routing?","**Minimal APIs** (ASP.NET Core 6+) let you define HTTP endpoints with lambda\nexpressions directly in `Program.cs`, without controllers, attributes, or\n`ControllerBase`. They are simpler and have less overhead.\n\n```csharp\n\u002F\u002F Minimal API — no controller class needed:\nvar builder = WebApplication.CreateBuilder(args);\nbuilder.Services.AddDbContext\u003CAppDb>(o => o.UseInMemoryDatabase(\"orders\"));\n\nvar app = builder.Build();\n\napp.MapGet(\"\u002Forders\", async (AppDb db) =>\n    await db.Orders.ToListAsync());\n\napp.MapGet(\"\u002Forders\u002F{id:int}\", async (int id, AppDb db) =>\n    await db.Orders.FindAsync(id) is Order o ? Results.Ok(o) : Results.NotFound());\n\napp.MapPost(\"\u002Forders\", async (CreateOrderDto dto, AppDb db) =>\n{\n    var order = new Order { Item = dto.Item };\n    db.Orders.Add(order);\n    await db.SaveChangesAsync();\n    return Results.Created($\"\u002Forders\u002F{order.Id}\", order);\n});\n\napp.Run();\n```\n\n| Aspect | Minimal API | Controller |\n|---|---|---|\n| Boilerplate | Minimal | More (class, `ControllerBase`, attributes) |\n| Built-in features | Manual | Model binding, ModelState, filters, conventions |\n| Testability | Lambda — needs `WebApplicationFactory` | Easier to unit-test in isolation |\n| Organisation | Flat in Program.cs | Class-based, scales better |\n| Best for | Microservices, simple endpoints | Complex domains with many actions |\n\n**Rule of thumb:** Start with minimal APIs for new small services and microservices.\nSwitch to controllers when you need structured organization, action filters, complex\nmodel binding, or per-action conventions across many endpoints.\n",{"id":56,"difficulty":14,"q":57,"a":58},"route-groups","What are route groups in minimal APIs and how do they reduce boilerplate?","**Route groups** (`MapGroup`) let you apply a common prefix, middleware, filters,\nand metadata to a set of related endpoints without repeating them on each one.\n\n```csharp\nvar api = app.MapGroup(\"\u002Fapi\");          \u002F\u002F common prefix for all API routes\nvar v1  = api.MapGroup(\"\u002Fv1\");           \u002F\u002F \u002Fapi\u002Fv1\nvar orders = v1.MapGroup(\"\u002Forders\")\n               .RequireAuthorization()   \u002F\u002F all \u002Fapi\u002Fv1\u002Forders endpoints need auth\n               .WithTags(\"Orders\");      \u002F\u002F Swagger tag\n\norders.MapGet(\"\u002F\", GetAllOrders);        \u002F\u002F GET \u002Fapi\u002Fv1\u002Forders\norders.MapGet(\"\u002F{id:int}\", GetOrder);    \u002F\u002F GET \u002Fapi\u002Fv1\u002Forders\u002F5\norders.MapPost(\"\u002F\", CreateOrder);        \u002F\u002F POST \u002Fapi\u002Fv1\u002Forders\norders.MapDelete(\"\u002F{id:int}\", DeleteOrder); \u002F\u002F DELETE \u002Fapi\u002Fv1\u002Forders\u002F5\n\n\u002F\u002F Endpoint filters apply to all endpoints in the group:\norders.AddEndpointFilter(async (ctx, next) =>\n{\n    Console.WriteLine($\"Before: {ctx.HttpContext.Request.Path}\");\n    var result = await next(ctx);\n    Console.WriteLine(\"After\");\n    return result;\n});\n```\n\nWithout groups, each `MapGet`\u002F`MapPost` would repeat `\u002Fapi\u002Fv1\u002Forders` and\n`.RequireAuthorization()`.\n\n**Rule of thumb:** Use `MapGroup` to co-locate related endpoints and apply\nshared policies (auth, rate limiting, OpenAPI tags) in one place. Group by\nresource or feature — mirroring what controllers do naturally.\n",{"id":60,"difficulty":14,"q":61,"a":62},"parameter-binding-sources","Where can ASP.NET Core bind route and action parameters from, and how does it decide?","ASP.NET Core binds parameters from multiple sources. For controllers with\n`[ApiController]`, the binding source is inferred; without it, you must specify.\n\n```csharp\n[HttpGet(\"products\u002F{id}\")]\npublic IActionResult Get(\n    int id,                              \u002F\u002F [FromRoute] inferred — from URL segment\n    [FromQuery] string? sort,            \u002F\u002F from ?sort=name\n    [FromHeader(Name=\"X-Version\")] string version, \u002F\u002F from request header\n    [FromServices] IProductRepo repo)    \u002F\u002F injected from DI\n    => Ok(repo.Find(id, sort));\n\n[HttpPost(\"products\")]\npublic IActionResult Create(\n    [FromBody] CreateProductDto dto)    \u002F\u002F JSON body — inferred for complex type\n    => Created($\"\u002Fproducts\u002F{dto.Id}\", dto);\n\n\u002F\u002F Form data:\n[HttpPost(\"upload\")]\npublic IActionResult Upload(\n    [FromForm] string name,\n    IFormFile file) { ... }\n```\n\n**`[ApiController]` inference rules:**\n- Complex types → `[FromBody]`\n- `IFormFile` \u002F `IFormFileCollection` → `[FromForm]`\n- Route parameter matches parameter name → `[FromRoute]`\n- Everything else → `[FromQuery]`\n\nFor minimal APIs, parameters are bound the same way with additional support for\ndirect DI injection:\n\n```csharp\napp.MapGet(\"\u002Fproducts\u002F{id}\", (int id, IProductRepo repo) =>\n    repo.Find(id)); \u002F\u002F id from route, repo from DI\n```\n\n**Rule of thumb:** Use `[ApiController]` to get automatic binding inference. Add\nexplicit `[From*]` attributes when you need to override the default or when\nworking without `[ApiController]`.\n",{"id":64,"difficulty":65,"q":66,"a":67},"routing-ambiguity","hard","What happens when two routes are ambiguous and how does ASP.NET Core resolve conflicts?","When multiple routes match a request with equal specificity, ASP.NET Core throws\nan `AmbiguousMatchException` at runtime (not build time). You must resolve ambiguity\nby making routes more specific or by ordering them.\n\n```csharp\n\u002F\u002F Ambiguous: both match GET \u002Fapi\u002Fproducts\u002F5\n[HttpGet(\"{id}\")]\npublic IActionResult GetById(string id) { ... }\n\n[HttpGet(\"{name}\")]\npublic IActionResult GetByName(string name) { ... }\n\u002F\u002F ↑ AmbiguousMatchException — the runtime can't distinguish these\n\n\u002F\u002F Fix 1: use route constraints to differentiate:\n[HttpGet(\"{id:int}\")]              \u002F\u002F only matches integers → \u002Fproducts\u002F5\npublic IActionResult GetById(int id) { ... }\n\n[HttpGet(\"{name:alpha}\")]          \u002F\u002F only matches letters → \u002Fproducts\u002Fwidget\npublic IActionResult GetByName(string name) { ... }\n\n\u002F\u002F Fix 2: use different path templates:\n[HttpGet(\"by-id\u002F{id:int}\")]\npublic IActionResult GetById(int id) { ... }\n\n[HttpGet(\"by-name\u002F{name}\")]\npublic IActionResult GetByName(string name) { ... }\n```\n\nConventional routing processes routes in registration order — first match wins\n— so ordering matters there:\n\n```csharp\n\u002F\u002F More specific pattern FIRST:\napp.MapControllerRoute(\"specific\", \"products\u002Ffeatured\", ...);\napp.MapControllerRoute(\"default\",  \"products\u002F{id}\",     ...);\n```\n\n**Rule of thumb:** Design routes to be unambiguous using constraints or distinct\ntemplates. Rely on registration order only as a last resort — it is implicit and\neasy to break when adding new routes.\n",{"id":69,"difficulty":14,"q":70,"a":71},"fallback-routes","What is a fallback route and how do you configure one in ASP.NET Core?","A **fallback route** is matched only when no other endpoint matches the request.\nIt is commonly used in Single-Page Applications to serve `index.html` for every\nunmatched path so client-side routing (React Router, Vue Router) can take over.\n\n```csharp\n\u002F\u002F MapFallback — minimal API style:\napp.MapControllers();       \u002F\u002F controller endpoints matched first\n\n\u002F\u002F MapFallbackToFile: serves a static file for any unmatched path\napp.MapFallbackToFile(\"index.html\"); \u002F\u002F SPAs: catch all and serve the shell\n\n\u002F\u002F MapFallbackToController: forwards unmatched routes to a controller action\napp.MapFallbackToController(\"Index\", \"Home\");\n\u002F\u002F Any unmatched request → HomeController.Index\n\n\u002F\u002F MapFallback with a handler:\napp.MapFallback(async ctx =>\n{\n    ctx.Response.StatusCode = 404;\n    await ctx.Response.WriteAsJsonAsync(new\n    {\n        error = \"Route not found\",\n        path  = ctx.Request.Path.Value\n    });\n});\n\n\u002F\u002F Fallback for a specific prefix only:\napp.MapFallback(\"\u002Fapp\u002F{**slug}\", async ctx =>\n    await ctx.Response.WriteAsync(\"SPA shell\"));\n\u002F\u002F \u002Fapp\u002Fdashboard, \u002Fapp\u002Fsettings → all served by SPA shell\n\u002F\u002F Other paths → normal 404\n```\n\nFallbacks have **lower priority than any other endpoint**, including wildcard routes,\nso they never shadow real endpoints.\n\n**Rule of thumb:** Use `MapFallbackToFile(\"index.html\")` for SPA hosting. Use\n`MapFallback` with a custom handler to return a structured JSON 404 for APIs.\nNever use a catch-all route constraint (`{**}`) on a real controller action as a\nsubstitute — use the proper fallback API.\n",{"id":73,"difficulty":14,"q":74,"a":75},"endpoint-metadata","What is endpoint metadata and how do middleware components use it?","**Endpoint metadata** is information attached to a matched endpoint at routing time.\nIt includes attributes (`[Authorize]`, `[AllowAnonymous]`, `[RequireCors]`, OpenAPI\ntags, etc.) and custom data added with `WithMetadata`. Middleware reads metadata from\n`IEndpointFeature` to make decisions without inspecting the URL.\n\n```csharp\n\u002F\u002F Attach metadata to a minimal API endpoint:\napp.MapGet(\"\u002Forders\", GetOrders)\n   .RequireAuthorization(\"AdminOnly\")    \u002F\u002F adds AuthorizeAttribute metadata\n   .WithName(\"get-orders\")               \u002F\u002F adds IEndpointNameMetadata\n   .WithTags(\"Orders\")                   \u002F\u002F adds ITagsMetadata (OpenAPI)\n   .WithMetadata(new AuditAttribute());  \u002F\u002F custom metadata\n\n\u002F\u002F Middleware reading endpoint metadata after UseRouting:\napp.UseRouting(); \u002F\u002F route matching happens here — metadata is now available\n\napp.Use(async (ctx, next) =>\n{\n    var endpoint = ctx.GetEndpoint();\n    if (endpoint is not null)\n    {\n        \u002F\u002F Check for a custom audit attribute on the matched endpoint:\n        var audit = endpoint.Metadata.GetMetadata\u003CAuditAttribute>();\n        if (audit is not null)\n            await AuditLogAsync(ctx.Request.Path);\n    }\n    await next(ctx);\n});\n\napp.UseAuthorization(); \u002F\u002F reads AuthorizeAttribute metadata set by UseRouting\n\napp.MapControllers();\n```\n\nWithout `UseRouting` running first, `ctx.GetEndpoint()` returns `null` because no\nroute has been matched yet.\n\n**Rule of thumb:** Place any middleware that reads endpoint metadata **after**\n`UseRouting` and **before** `MapControllers`. This is the window where the endpoint\nis known but not yet executed.\n",{"id":77,"difficulty":14,"q":78,"a":79},"route-naming-and-order","How do you control the order and priority of routes in ASP.NET Core's endpoint routing?","Endpoint routing uses a **precedence algorithm** to rank routes, not simple\nregistration order. Literal segments rank higher than parameters, and constrained\nparameters rank higher than unconstrained ones. Registration order is used only\nas a tiebreaker when two routes are equally specific.\n\n```csharp\n\u002F\u002F Endpoint routing precedence examples (higher beats lower):\n\u002F\u002F 1. Literal segment:    \u002Fproducts\u002Ffeatured  (most specific)\n\u002F\u002F 2. Constrained param:  \u002Fproducts\u002F{id:int}\n\u002F\u002F 3. Unconstrained param:\u002Fproducts\u002F{id}\n\u002F\u002F 4. Catch-all:          \u002Fproducts\u002F{**rest}  (least specific)\n\n\u002F\u002F All registered in any order — routing picks correctly:\napp.MapGet(\"\u002Fproducts\u002Ffeatured\", GetFeatured);   \u002F\u002F literal — wins for \u002Fproducts\u002Ffeatured\napp.MapGet(\"\u002Fproducts\u002F{id:int}\", GetById);       \u002F\u002F constrained — wins for \u002Fproducts\u002F5\napp.MapGet(\"\u002Fproducts\u002F{slug}\",   GetBySlug);     \u002F\u002F unconstrained — wins for \u002Fproducts\u002Fwidget\napp.MapGet(\"\u002Fproducts\u002F{**rest}\", GetSubPath);    \u002F\u002F catch-all — last resort\n\n\u002F\u002F Explicit Order property to force rank when needed:\napp.MapGet(\"\u002Fdebug\", DebugHandler)\n   .WithOrder(-1); \u002F\u002F negative = higher priority; runs before default (0)\n\napp.MapControllerRoute(\n    name: \"default\",\n    pattern: \"{controller=Home}\u002F{action=Index}\u002F{id?}\");\n\u002F\u002F Conventional routes matched after attribute routes by default\n```\n\nFor conventional routes, registration order matters within the same specificity\ntier — register more specific patterns first:\n\n```csharp\n\u002F\u002F Specific conventional pattern must come before generic one:\napp.MapControllerRoute(\"blog\", \"blog\u002F{year:int}\u002F{slug}\", new { controller = \"Blog\", action = \"Post\" });\napp.MapControllerRoute(\"default\", \"{controller=Home}\u002F{action=Index}\u002F{id?}\");\n```\n\n**Rule of thumb:** Trust endpoint routing's precedence algorithm for attribute\nroutes — it handles most cases without explicit ordering. Add `.WithOrder()` only\nwhen you have a genuine tie that the algorithm cannot break. For conventional\nroutes, always register more specific patterns first.\n",{"id":81,"difficulty":14,"q":82,"a":83},"openapi-metadata","How do you add OpenAPI metadata to minimal API endpoints for Swagger documentation?","Minimal APIs use extension methods from `Microsoft.AspNetCore.OpenApi` (.NET 9) or\nSwashbuckle to attach **OpenAPI metadata** — names, descriptions, tags, response\ntypes, and operation IDs — directly to endpoint definitions.\n\n```csharp\n\u002F\u002F Install: dotnet add package Microsoft.AspNetCore.OpenApi  (.NET 9+)\nbuilder.Services.AddOpenApi(); \u002F\u002F registers OpenAPI document generation\n\napp.MapOpenApi(); \u002F\u002F exposes \u002Fopenapi\u002Fv1.json\n\n\u002F\u002F Attach metadata to individual endpoints:\napp.MapGet(\"\u002Fproducts\u002F{id:int}\", async (int id, IProductRepo repo) =>\n    await repo.FindAsync(id) is Product p ? Results.Ok(p) : Results.NotFound())\n   .WithName(\"GetProduct\")                \u002F\u002F operation id for link generation\n   .WithSummary(\"Get a product by ID\")    \u002F\u002F short description in Swagger UI\n   .WithDescription(\"Returns the product matching the given integer ID, or 404.\")\n   .WithTags(\"Products\")                  \u002F\u002F groups endpoints in Swagger UI\n   .Produces\u003CProduct>(StatusCodes.Status200OK)\n   .Produces(StatusCodes.Status404NotFound)\n   .RequireAuthorization();\n\napp.MapPost(\"\u002Fproducts\", async (CreateProductDto dto, IProductRepo repo) =>\n{\n    var product = await repo.CreateAsync(dto);\n    return Results.CreatedAtRoute(\"GetProduct\", new { id = product.Id }, product);\n})\n   .WithName(\"CreateProduct\")\n   .WithTags(\"Products\")\n   .Accepts\u003CCreateProductDto>(\"application\u002Fjson\")   \u002F\u002F documents request body type\n   .Produces\u003CProduct>(StatusCodes.Status201Created)\n   .Produces\u003CValidationProblemDetails>(StatusCodes.Status400BadRequest);\n\n\u002F\u002F Apply metadata to a whole group:\nvar v1 = app.MapGroup(\"\u002Fapi\u002Fv1\")\n            .WithTags(\"v1\")\n            .WithOpenApi(); \u002F\u002F enables OpenAPI for all endpoints in the group\n```\n\n**Rule of thumb:** Always attach `.WithName`, `.WithTags`, `.Produces\u003CT>`, and\n`.WithSummary` to every public endpoint. Accurate metadata drives Swagger UI,\nclient SDK generation, and integration test contracts — treat it as part of the\nendpoint definition, not an afterthought.\n",15,null,{"description":11},"ASP.NET Core routing interview questions — attribute vs conventional routing, route constraints, endpoint routing, link generation, and minimal APIs.","dotnet\u002Faspnet-core\u002Frouting","ASP.NET Core","aspnet-core","2026-06-23","CoFNNHGBlyxWZipUlBETmBmbKu-a6eXzliQPovucrQo",[94,98,99,103],{"subtopic":95,"path":96,"order":97},"Middleware","\u002Fdotnet\u002Faspnet-core\u002Fmiddleware",1,{"subtopic":6,"path":20,"order":12},{"subtopic":100,"path":101,"order":102},"Controllers & Actions","\u002Fdotnet\u002Faspnet-core\u002Fcontrollers-actions",3,{"subtopic":104,"path":105,"order":106},"Configuration","\u002Fdotnet\u002Faspnet-core\u002Fconfiguration",4,{"path":108,"title":109},"\u002Fblog\u002Fdotnet-aspnet-core-routing","Routing in ASP.NET Core",1782244118116]