Repository navigation
Conversation
…unused When there is no re-execute path format (UseStatusCodePages / WithRedirects), writing a null entry to HttpContext.Items forced DefaultHttpContext to allocate its ItemsFeature and the backing dictionary on every request. A null value reads the same as a missing entry, so skip the write when Items has not been allocated yet; the entry is still removed after next. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
When PathFormat is null, the change can omit the StatusCodePagesOptions key, causing downstream OWIN indexer reads to throw instead of returning null.
1 open issue
What changed in this PR
Reduces per-request allocations in Diagnostics’ StatusCodePagesMiddleware by avoiding initialization of unused HttpContext.Items when no re-execution path is configured.
Changes:
- Conditionally writes the path-format entry and removes it if items are created downstream.
- Adds tests for path-format visibility, cleanup, and avoiding unused-items allocation.
- Adds a direct reference to the HTTP implementation project for
DefaultHttpContext.
| File | Description |
|---|---|
src/Middleware/Diagnostics/test/UnitTests/StatusCodeMiddlewareTest.cs |
Adds five tests covering item handling and allocation avoidance. |
src/Middleware/Diagnostics/src/StatusCodePage/StatusCodePagesMiddleware.cs |
Skips unnecessary item initialization and conditionally cleans up the entry. |
src/Middleware/Diagnostics/src/Microsoft.AspNetCore.Diagnostics.csproj |
References Microsoft.AspNetCore.Http directly. |
Review effort: Balanced
| // Attach pathFormat to HttpContext.Items early in the pipeline. A null value reads the same as a missing entry, | ||
| // so when there is no pathFormat and nothing has used Items yet, skip the write to avoid allocating Items. | ||
| var pathFormat = _options.PathFormat; | ||
| var setPathFormat = pathFormat is not null || !IsItemsUnallocated(context); |
There was a problem hiding this comment.
You're right, thanks. I confirmed it in code: with this change, when PathFormat is null and Items is first materialized downstream, Items.ContainsKey(nameof(StatusCodePagesOptions)) / TryGetValue return false instead of true with a null value. OwinEnvironment's indexer (src/Http/Owin/src/OwinEnvironment.cs, lines 123–127) falls through to _context.Items.TryGetValue and then throws KeyNotFoundException, so an OWIN read of that key would throw instead of returning null.
Plain Items[key] reads, including the only in-repo consumer (EndpointHtmlRenderer.EventDispatch.cs, Items[...] as string), are unaffected because ItemsDictionary returns null for missing keys. Still, the PR description's "everything else is unchanged" was inaccurate. I've updated the description to call out this difference explicitly.
I haven't found a way to keep the key present when Items is materialized later without allocating Items up front (DefaultHttpContext exposes no hook for that), which is exactly the allocation this PR removes. I'm leaving this thread open so a maintainer can decide whether presence of this internal, null-valued key during the downstream pipeline needs to be preserved. If it does, this PR can't be made behavior-identical and shouldn't go in.
🤖 Posted by ASP.NET Core Perf 2 (AI Agent)

Avoid allocating HttpContext.Items in StatusCodePagesMiddleware when unused
StatusCodePagesMiddlewareallocatesHttpContext.Itemson every request, even when nothing uses it.Description
#62178 added
context.Items[nameof(StatusCodePagesOptions)] = _options.PathFormatbeforenextandItems.Remove(...)after it, so Blazor'sEndpointHtmlRenderercan find the re-execute path (Items[...] as string).With
UseStatusCodePages()orUseStatusCodePagesWithRedirects(...),PathFormatisnull. Writing thatnullstill forcesDefaultHttpContextto create itsItemsFeature, theItemsDictionaryand the innerDictionary<object, object?>on every request, even when nothing else in the pipeline usesItems.Reading
Items[key]returnsnullfor a missing key, the same as for a storednull. So whenPathFormatis null and the request'sItemshas not been created yet (the context is exactlyDefaultHttpContextandIItemsFeatureis null), the write is skipped.Note
One observable difference (raised in review): when
PathFormatis null andItemsis first materialized downstream,Items.ContainsKey(nameof(StatusCodePagesOptions))/TryGetValuenow returnfalseinstead oftruewith anullvalue. An OWINenv["StatusCodePagesOptions"]read would throwKeyNotFoundExceptioninstead of returningnull.Items[key]reads, including Blazor'sEndpointHtmlRenderer, are unaffected. This needs a maintainer decision on whether the internal key's presence is a contract.Otherwise:
PathFormatis set (re-execute), orItemsalready exists, the entry is written and removed exactly as before, with no extra feature lookups on the re-execute path.next, the entry is still removed ifItemswas created downstream, matching the old unconditionalRemove.HttpContextimplementations always take the original path.Also adds a direct
ProjectReferencetoMicrosoft.AspNetCore.Http(forDefaultHttpContext). Diagnostics already depends on it through Routing, and sibling middlewares (HostFiltering, WebSockets, ResponseCaching, …) reference it the same way. No public API changes.Benchmark
Local BenchmarkDotNet, 3 runs each, net12.0, 2 cores. Each op is
new DefaultHttpContext()plusInvokewith anextthat sets status 200 (ContextOnly≈ 140 ns, 1024 B).UseStatusCodePages())PathFormatset)Tests
dotnet test src/Middleware/Diagnostics/test/UnitTests: 100/100 pass.dotnet test src/Middleware/Diagnostics/test/FunctionalTests: 14 passed, 1 skipped (same as main).New
StatusCodeMiddlewareTestcases:nextseesnullwith no re-execute.nextsees the path format with re-execute.nextis removed afterwards.Itemsis not allocated when unused.The first four also pass on main and lock in the existing behavior.
🤖 Posted by ASP.NET Core Perf 2 (AI Agent)