Scenario contact: @javiercn
Scenario
Uptime monitors, load balancer probes, link checkers and caches ask a page for its headers with an HTTP HEAD request. In .NET 10 Razor component endpoints advertised only GET and POST, so every one of those requests came back 405 Method Not Allowed, which showed up as false alerts and broken-link reports. .NET 11 adds HEAD to the endpoint's method metadata. A HEAD request now runs the page exactly as GET does and the server drops the body.
Minimum build
.NET 11 Preview 7 or later.
Configurations to cover
The response to a HEAD request is produced entirely on the server, so the two configurations above are enough. Interactive WebAssembly and Auto reach the same server-side path as Interactive Server.
Also exercise
Setup
You need curl, or an HTTP client that can send HEAD and show you raw response headers. Browsers cannot send HEAD from the address bar, so this scenario is driven from the command line.
What to build
One Blazor Web App with these routes. Keep them trivial; the point is the response, not the page.
| Route |
What it is |
/plain |
A page with a heading and nothing else |
/item/42 |
A page taking a route parameter, rendering the value |
/slow |
A page using @attribute [StreamRendering] that awaits 2 seconds before showing data |
/secure |
A page with @attribute [Authorize] |
/teapot |
A page that sets HttpContext.Response.StatusCode = 418 and adds a header X-Test: hello |
/missing-route |
Nothing. No page declares this route |
/logo.png |
Any static file, as a control that never went through the component endpoint |
Add a counter that every page increments when it renders, so you can tell whether a request executed the page or merely returned headers:
// Program.cs
builder.Services.AddSingleton<HitCounter>();
...
app.MapGet("/hits", (HitCounter c) => c.Count);
public class HitCounter
{
private int _count;
public int Count => _count;
public void Increment() => Interlocked.Increment(ref _count);
}
Inject it into each page and call Counter.Increment() from OnInitialized.
Things to try
Send each route both ways and compare. curl -I sends HEAD; curl -s -o /dev/null -D - sends GET and prints only the headers, so the two outputs are directly comparable:
curl -I http://localhost:5000/plain # HEAD
curl -s -o /dev/null -D - http://localhost:5000/plain # GET, headers only
Use curl -I rather than curl -X HEAD. The latter sends the right method but then waits for a body that never arrives, so it hangs until it times out.
Work through the table above with both commands, then:
- Read
/hits before and after a HEAD request to /plain, to see whether the page ran.
- Time the
HEAD request to /slow with curl -o /dev/null -w '%{time_total}\n' -I http://localhost:5000/slow and compare it with the same GET.
- Repeat the
/plain and /secure requests against the app running on .NET 10, where both return 405.
- With the app running under Hot Reload, add a new
@page route and change an existing one, then send HEAD to both without restarting.
Expected behavior
A HEAD request is answered exactly as the matching GET would be, minus the response body.
Must hold
HEAD /plain returns 200, Content-Type: text/html; charset=utf-8, and an empty body.
- For every route in the table, the status code returned by
HEAD matches the status code returned by GET: 200 for /plain, /item/42 and /slow, 418 for /teapot, and whatever /secure returns when signed out, which is normally a 302 redirect to the login page.
HEAD /teapot carries the X-Test: hello header, so a page's own headers survive.
HEAD /missing-route, which matches no page, returns 404 rather than 405.
/hits increases by one after a HEAD request, confirming the page executed rather than being short-circuited.
HEAD /slow takes about as long as GET /slow, since streaming rendering still runs.
- No response to a
HEAD request carries a body, and no HEAD request produces a 405.
- Nothing appears in the server log for a
HEAD request that does not also appear for the matching GET.
Expected differences between configurations
- None between Static SSR and Interactive Server. If a status code or header differs between them for the same route, that is a finding.
- On .NET 10 every one of these returns
405. That is the behavior being fixed, not a failure.
Evidence to capture
For each route, the full HEAD and GET header output pasted as text, so the two can be compared line by line. Include the /hits values read before and after.
Documentation to use
What to report
Report results using the format described in the validation testing manual. Include link to a repository with the test app.
Scenario contact: @javiercn
Scenario
Uptime monitors, load balancer probes, link checkers and caches ask a page for its headers with an HTTP
HEADrequest. In .NET 10 Razor component endpoints advertised onlyGETandPOST, so every one of those requests came back405 Method Not Allowed, which showed up as false alerts and broken-link reports. .NET 11 addsHEADto the endpoint's method metadata. AHEADrequest now runs the page exactly asGETdoes and the server drops the body.Minimum build
.NET 11 Preview 7 or later.
Configurations to cover
The response to a
HEADrequest is produced entirely on the server, so the two configurations above are enough. Interactive WebAssembly and Auto reach the same server-side path as Interactive Server.Also exercise
Setup
You need
curl, or an HTTP client that can sendHEADand show you raw response headers. Browsers cannot sendHEADfrom the address bar, so this scenario is driven from the command line.What to build
One Blazor Web App with these routes. Keep them trivial; the point is the response, not the page.
/plain/item/42/slow@attribute [StreamRendering]that awaits 2 seconds before showing data/secure@attribute [Authorize]/teapotHttpContext.Response.StatusCode = 418and adds a headerX-Test: hello/missing-route/logo.pngAdd a counter that every page increments when it renders, so you can tell whether a request executed the page or merely returned headers:
Inject it into each page and call
Counter.Increment()fromOnInitialized.Things to try
Send each route both ways and compare.
curl -IsendsHEAD;curl -s -o /dev/null -D -sendsGETand prints only the headers, so the two outputs are directly comparable:Use
curl -Irather thancurl -X HEAD. The latter sends the right method but then waits for a body that never arrives, so it hangs until it times out.Work through the table above with both commands, then:
/hitsbefore and after aHEADrequest to/plain, to see whether the page ran.HEADrequest to/slowwithcurl -o /dev/null -w '%{time_total}\n' -I http://localhost:5000/slowand compare it with the sameGET./plainand/securerequests against the app running on .NET 10, where both return405.@pageroute and change an existing one, then sendHEADto both without restarting.Expected behavior
A
HEADrequest is answered exactly as the matchingGETwould be, minus the response body.Must hold
HEAD /plainreturns200,Content-Type: text/html; charset=utf-8, and an empty body.HEADmatches the status code returned byGET:200for/plain,/item/42and/slow,418for/teapot, and whatever/securereturns when signed out, which is normally a302redirect to the login page.HEAD /teapotcarries theX-Test: helloheader, so a page's own headers survive.HEAD /missing-route, which matches no page, returns404rather than405./hitsincreases by one after aHEADrequest, confirming the page executed rather than being short-circuited.HEAD /slowtakes about as long asGET /slow, since streaming rendering still runs.HEADrequest carries a body, and noHEADrequest produces a405.HEADrequest that does not also appear for the matchingGET.Expected differences between configurations
405. That is the behavior being fixed, not a failure.Evidence to capture
For each route, the full
HEADandGETheader output pasted as text, so the two can be compared line by line. Include the/hitsvalues read before and after.Documentation to use
What to report
Report results using the format described in the validation testing manual. Include link to a repository with the test app.