Skip to content

Repository files navigation

http-query-net

CI

Language: English · Español

HTTP QUERY — the verb HTTP was missing: reads with a body, giving up nothing. Safe, idempotent, cacheable, with a body.

A hands-on, runnable tour of the HTTP QUERY method 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 --reverse walks you through the whole thing from an empty folder to a working system.


Why QUERY exists

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 ⚠️ rarely
QUERY

QUERY is "a GET that's allowed to carry a body." Same read-only guarantees, no URL limits.

The deeper dive lives in docs/http-query.md.


What's in this repo

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

Prerequisites

  • .NET 10 SDK (dotnet --version10.0.x)
  • For the Aspire experience, trust the local dev certificate once:
    dotnet dev-certs https --trust

Run it

Option A — everything at once (.NET Aspire) 🚀

dotnet run --project src/HttpQueryDemo.AppHost

Opens 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.

Option B — API + browser playground

dotnet run --project src/HttpQueryDemo.Api

Then 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.

Option C — just the console client

With the API already running (Option B), in another terminal:

dotnet run --project src/HttpQueryDemo.Client

Run the tests

dotnet test

How it works

Server — HttpMethods.Query + MapMethods

ASP.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

Client — HttpMethod.Query

.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").

Browser — fetch({ method: "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),
});

wwwroot/index.html


.NET support at a glance

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.


The commit-by-commit tour

This repo's history is the tutorial. Read it in order:

git log --reverse --oneline

Each commit compiles and adds exactly one idea — scaffold → contracts → the QUERY endpoint → the playground → the client → Aspire → tests → these docs.


Further reading

License

MIT — see LICENSE.

About

Hands-on demo of the HTTP QUERY method (RFC 10008) in .NET 10 — Minimal API, HttpClient, browser playground, tests and .NET Aspire orchestration

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages