Language: English · Español
A hands-on, runnable tour of the HTTP
QUERYmethod in .NET 10 — server, typed client, an interactive browser playground, integration tests, and one-command .NET Aspire orchestration.It's built to be read as much as run: every commit is a chapter, so
git log --reversewalks you through the whole thing from an empty folder to a working system.
You need to run a rich, read-only search — many ids, several facets, a price range, sorting, paging. You have two classic options, and both are bad:
| Approach | The problem |
|---|---|
GET with a query string |
Complex criteria balloon the URL. You hit URL length limits, fight with encoding arrays/objects, and leak filter values into logs and browser history. A body on GET has no defined semantics. |
POST with a body |
Room for any query — but POST is not safe, not idempotent, and not cacheable. You've now told every cache, proxy and reader that this read changes state. Retries become risky; caching is off the table. |
QUERY (RFC 10008, June 2026) is
the verb designed for exactly this gap:
| HTTP method | Safe | Idempotent | Cacheable | Has a request body |
|---|---|---|---|---|
GET |
✅ | ✅ | ✅ | ❌ (undefined) |
POST |
❌ | ❌ | ✅ | |
QUERY |
✅ | ✅ | ✅ | ✅ |
QUERY is "a
GETthat's allowed to carry a body." Same read-only guarantees, no URL limits.
The deeper dive lives in docs/http-query.md.
src/
HttpQueryDemo.Contracts/ Shared DTOs (ProductQuery, ProductQueryResult, Product)
HttpQueryDemo.Api/ Minimal API: QUERY endpoint + interactive playground
HttpQueryDemo.Client/ Console app: HttpMethod.Query via a typed HttpClient
HttpQueryDemo.ServiceDefaults/ Aspire shared defaults (telemetry, health, discovery)
HttpQueryDemo.AppHost/ Aspire orchestrator (one command runs everything)
tests/
HttpQueryDemo.Tests/ xUnit integration tests over the QUERY endpoint
docs/
http-query.md The concepts, in depth, plus a production checklist
- .NET 10 SDK (
dotnet --version→10.0.x) - For the Aspire experience, trust the local dev certificate once:
dotnet dev-certs https --trust
dotnet run --project src/HttpQueryDemo.AppHostOpens the Aspire dashboard. You'll see two resources: catalog-api and
catalog-client. The client waits for the API to be healthy, then runs its
QUERY scenarios once — watch the logs and traces flow through the dashboard.
Open the API resource's URL to reach the playground.
dotnet run --project src/HttpQueryDemo.ApiThen open http://localhost:5011. Build a query, hit Send QUERY, and watch
the browser issue a real QUERY request (yes, fetch sends custom verbs). The
page shows the raw request line, the JSON response, and rendered results.
With the API already running (Option B), in another terminal:
dotnet run --project src/HttpQueryDemo.Clientdotnet testASP.NET Core 10 ships the first-class constant HttpMethods.Query (and
HttpMethods.IsQuery(...)), so you register the verb without magic strings:
app.MapMethods("/products/query", [HttpMethods.Query],
(ProductCatalog catalog, [FromBody] ProductQuery query) =>
Results.Ok(catalog.Query(query)));The ProductQuery binds straight from the JSON request body — minimal APIs treat
a QUERY body exactly like a POST body, no special handling required.
→ ProductQueryEndpoints.cs
.NET 10 adds the static HttpMethod.Query:
using var request = new HttpRequestMessage(HttpMethod.Query, "products/query")
{
Content = JsonContent.Create(query),
};
var response = await http.SendAsync(request);Because QUERY is idempotent, the client safely layers on retries via the
standard resilience handler — something you should never do blindly with POST.
→ CatalogClient.cs
Pre-.NET 10? The same wire request is
new HttpMethod("QUERY").
The Fetch API happily sends any valid custom method, so the browser is a first-class QUERY client with no polyfill:
await fetch("/products/query", {
method: "QUERY",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(query),
});| Piece | Where | Since |
|---|---|---|
HttpMethod.Query |
System.Net.Http (client) |
.NET 10 |
HttpMethods.Query / HttpMethods.IsQuery |
Microsoft.AspNetCore.Http |
ASP.NET Core 10 |
| Registering the endpoint | MapMethods(route, [HttpMethods.Query], …) |
works today |
The ecosystem (OpenAPI tooling, CDNs, WAFs, some browsers' dev UIs) is still catching up — see the production checklist.
This repo's history is the tutorial. Read it in order:
git log --reverse --onelineEach commit compiles and adds exactly one idea — scaffold → contracts → the QUERY endpoint → the playground → the client → Aspire → tests → these docs.
- RFC 10008 — The HTTP QUERY Method
HttpMethod.QueryAPI referenceHttpMethods.QueryAPI reference- HTTP support in .NET
MIT — see LICENSE.
