v11 update: Endpoint filters observe parameter-binding failures
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Aptitud para principiantes
- 76/100
- Tipo de issue
- Documentación
- Claridad
- Bien especificado
- Estado de actividad
- Activo
- Stack tecnológico
- csharp
- Área
- documentation
Línea de trabajo
Start with aspnetcore/fundamentals/minimal-apis/min-api-filters.md around lines 116–122 and the Binding failures section in includes/parameter-binding8-10.md around lines 421–431. Verify the documented behavior against the cited release note and source details, including the aspnetcore-8.0 and aspnetcore-11.0 moniker ranges. Done means both articles explain filter behavior, status handling, ThrowOnBadRequest, and the .NET 11 fix accurately without breaking moniker zones.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
[!NOTE]
This is an AI-assisted coverage analysis created by wadepickett.
Target repository: dotnet/AspNetCore.Docs
Analyzed at commit: 4986136881f2bbf1879f10106467769df5a49932
Product source verified at: dotnet/aspnetcore @ 1fcd7ef305697a1888f3ede076010350ae9f4f8d
Source release note: Endpoint filters observe parameter-binding failures
🎯 Goal
This sub-area is almost entirely undocumented, and the release note describes it inaccurately. 2 of the 11 inventoried elements are covered. More importantly, product source shows the release note's framing is wrong in a way that will propagate into the docs if it's copied:
- The capability isn't new in .NET 11. Endpoint filters have observed parameter-binding failures since .NET 8 —
Endpoint filter pipeline now returns EmptyHttpResult in case of 400 BadRequest (#45073)introduced theStatusCode >= 400guard that both sets the 400 and suppresses the handler. Writing this up as "new in .NET 11" would be wrong for every reader on .NET 8, 9, and 10 who already has this behavior. - What actually changed in .NET 11 is a bug fix, and it's the opposite of what the note implies. Before
#64539, an endpoint whose filter factories all returnednextunchanged (so no filter pipeline was built) would set a 400 on a binding failure and still execute the route handler with unbound arguments. The fix stops the handler from running. The added regression tests are namedParameterBindingFailure_WithEndpointFilterFactory_DoesNotExecuteHandler. - The status-code guard is
>= 400, not== 400. The release note tells readers to checkHttpContext.Response.StatusCode == 400. That works for binding failures specifically, but the framework's own suppression check is>=, and a filter earlier in the pipeline can set a different code.
RouteHandlerOptions.ThrowOnBadRequest — the one piece of guidance in the note a developer genuinely can't discover elsewhere — appears zero times anywhere in aspnetcore/ outside the release notes.
Gaps this report closes:
aspnetcore/fundamentals/minimal-apis/min-api-filters.mdnever mentions that a filter runs when binding fails, or how to detect it.RouteHandlerOptionsandThrowOnBadRequestare undocumented in the docset, including the environment-dependent default.- The "Binding failures" reference table doesn't tell a reader that a filter can intercept the 400 it describes.
✅ Coverage status summary
Legend: ✅ already documented · ✏️ update needed · 🟣 could not determine
| # | Feature element from What's New | Status | Where |
|---|---|---|---|
| 1 | Behavior: the filter pipeline runs even when parameter binding fails (pre-.NET 11 capability, undocumented) | ✏️ | 1. Update — aspnetcore/fundamentals/minimal-apis/min-api-filters.md L116–L122 |
| 2 | Condition: the endpoint has at least one filter or filter factory that actually builds a pipeline | ✏️ | 1. Update |
| 3 | A filter reads HttpContext.Response.StatusCode to detect the failure (framework guard is >= 400) |
✏️ | 1. Update |
| 4 | The route handler is not invoked once the status is >= 400; the filter can substitute a response body |
✏️ | 1. Update |
| 5 | RouteHandlerOptions — the options type itself |
✏️ | 2. Update — aspnetcore/fundamentals/minimal-apis/min-api-filters.md L116–L122 |
| 6 | RouteHandlerOptions.ThrowOnBadRequest = false so the filter observes a 400 instead of an exception |
✏️ | 2. Update |
| 7 | Default: ThrowOnBadRequest defaults to IHostEnvironment.IsDevelopment() |
✏️ | 2. Update |
| 8 | BadHttpRequestException reaches the developer exception page in Development |
✏️ | 2. Update |
| 9 | .NET 11 fix: a pass-through filter factory no longer lets the handler run after a binding failure | ✏️ | 3. Update — aspnetcore/fundamentals/minimal-apis/min-api-filters.md L116–L122 |
| 10 | Binding failure modes and the status codes they produce | ✅ | aspnetcore/fundamentals/minimal-apis/includes/parameter-binding8-10.md L421–L431 |
| 11 | A pass-through filter is registered when a factory finds no matching signature | ✅ | aspnetcore/fundamentals/minimal-apis/min-api-filters.md L120 |
🔢 Version applicability
Applies to: RETROACTIVE
Target moniker: >= aspnetcore-8.0 for changes 1 and 2; >= aspnetcore-11.0 for change 3
Earlier versions affected: .NET 8, .NET 9, .NET 10 — the StatusCode >= 400 guard and the "set 400 but keep going so filters run" path both date to #45073 (f69643b785), confirmed with git log -L 481,486:src/Http/Http.Extensions/src/RequestDelegateFactory.cs. RouteHandlerOptions.ThrowOnBadRequest is older still.
| Article | monikerRange |
Moniker state |
|---|---|---|
aspnetcore/fundamentals/minimal-apis/min-api-filters.md |
'>= aspnetcore-7.0' |
State B — the article contains no :::moniker directives at all. Changes 1–3 must open their own zones. Because the article's floor is 7.0 and the behavior starts at 8.0, the new content can't be left unzoned. |
aspnetcore/fundamentals/minimal-apis/includes/parameter-binding8-10.md |
(include; no front matter) | State C — the insertion point at L431 sits inside the >= aspnetcore-8.0 zone that spans L24–L494. The cross-reference applies to exactly that range, so insert directly with no split. |
📋 Coverage gap summary
A developer writes a Minimal API endpoint filter that validates or logs requests, then discovers the filter runs with Arguments holding default values and no obvious explanation. They open Filters in Minimal API apps — the canonical article for endpoint filters — and find nothing about parameter binding at all. Nothing tells them the framework already set HttpContext.Response.StatusCode to 400, that the handler won't run, or that they can write their own body. Separately, a developer debugging in Development sees BadHttpRequestException hit the developer exception page instead of reaching their filter, and the only remedy — RouteHandlerOptions.ThrowOnBadRequest = false — is not named anywhere in aspnetcore/.
Feature announced in What's New:
When a Minimal API endpoint has any filters or filter factories configured, the filter pipeline now runs even if parameter binding fails. Filters can read
HttpContext.Response.StatusCode == 400and substitute their own response body.In the
Developmentenvironment, setRouteHandlerOptions.ThrowOnBadRequest = falseso the framework returns a 400 that the filter can observe instead of throwingBadHttpRequestExceptionto the developer exception page. This is already the default in non-Developmentenvironments.
Breaking change: No for the documented capability. The .NET 11 fix is behavior-changing in a narrow case — an endpoint whose only filter factories return next unchanged no longer executes its route handler after a binding failure. Any app that relied on the handler running with unbound arguments in that configuration was relying on a bug.
📁 Affected files
| Item | Path | Lines | Section |
|---|---|---|---|
| 1., 2., 3. | aspnetcore/fundamentals/minimal-apis/min-api-filters.md |
116–122 | "Register a filter by using an endpoint filter factory" → "Register a filter on controller actions" |
| 4. | aspnetcore/fundamentals/minimal-apis/includes/parameter-binding8-10.md |
421–433 | "Binding failures" |
Target article uids: fundamentals/minimal-apis/min-api-filters, fundamentals/minimal-apis/parameter-binding (the include is surfaced through this uid)
📝 Proposed changes
✏️ 1. Update — aspnetcore/fundamentals/minimal-apis/min-api-filters.md, insert after line 120
Applies to: >= aspnetcore-8.0 (new zone; the article itself is >= aspnetcore-7.0 and unzoned)
Location: Line 120, immediately after the bullet beginning "If a matching signature isn't found".
Changes 1, 2, and 3 are contiguous and are presented here as one insertion so the zones stay balanced and readable.
Before (lines 116–122):
In the preceding code:
* The `EndpointFilterFactoryContext` object provides access to the [MethodInfo](/dotnet/api/system.reflection.methodinfo) associated with the endpoint's handler.
* The signature of the handler is examined by inspecting `MethodInfo` for the expected type signature. If the expected signature is found, the validation filter is registered onto the endpoint. This factory pattern is useful to register a filter that depends on the signature of the target endpoint handler.
* If a matching signature isn't found, a pass-through filter is registered.
## Register a filter on controller actions
After:
In the preceding code:
* The `EndpointFilterFactoryContext` object provides access to the [MethodInfo](/dotnet/api/system.reflection.methodinfo) associated with the endpoint's handler.
* The signature of the handler is examined by inspecting `MethodInfo` for the expected type signature. If the expected signature is found, the validation filter is registered onto the endpoint. This factory pattern is useful to register a filter that depends on the signature of the target endpoint handler.
* If a matching signature isn't found, a pass-through filter is registered.
:::moniker range=">= aspnetcore-8.0"
## Filters and parameter binding failures
Endpoint filters run even when parameter binding fails. When a filter pipeline is built for an endpoint and a parameter can't be bound, the framework:
* Sets `HttpContext.Response.StatusCode` to `400`.
* Invokes the filter pipeline.
* Skips the route handler. The innermost step of the pipeline returns an empty result instead of calling the handler whenever the response status code is `400` or greater.
A filter therefore sees the failed request and can replace the empty `400` with a response body of its own:
```csharp
app.MapGet("/products/{id}", (Guid id) => TypedResults.Ok(id))
.AddEndpointFilter(async (context, next) =>
{
if (context.HttpContext.Response.StatusCode >= StatusCodes.Status400BadRequest)
{
return TypedResults.Problem(
title: "The request couldn't be bound.",
statusCode: context.HttpContext.Response.StatusCode);
}
return await next(context);
});
```
Test the status code with `>=` rather than `== 400`. That's the same comparison the framework uses to decide whether to skip the handler, and a filter that runs earlier in the pipeline can set a different code.
Because the handler never ran, the values in `EndpointFilterInvocationContext.Arguments` are the parameters' default values, not bound request data. A filter that inspects arguments should check the status code first.
## Observe binding failures in the Development environment
<xref:Microsoft.AspNetCore.Routing.RouteHandlerOptions.ThrowOnBadRequest> controls whether the framework throws <xref:Microsoft.AspNetCore.Http.BadHttpRequestException> in addition to writing a `Debug`-level log when it handles an invalid request. It defaults to the value of <xref:Microsoft.Extensions.Hosting.HostEnvironmentEnvExtensions.IsDevelopment%2A>, so it's `true` in the `Development` environment and `false` everywhere else.
In `Development`, that default means the exception reaches the developer exception page before a filter can respond. Set the option to `false` to get the `400` the filter can observe:
```csharp
builder.Services.Configure<RouteHandlerOptions>(options =>
{
options.ThrowOnBadRequest = false;
});
```
No change is needed in other environments, where `false` is already the default.
:::moniker-end
:::moniker range=">= aspnetcore-11.0"
> [!NOTE]
> In .NET 11 or later, an endpoint whose filter factories all return `next` unchanged — so that no filter pipeline is actually built — no longer runs its route handler after a parameter-binding failure. In earlier releases, that configuration set the `400` status code and then invoked the handler anyway with unbound argument values. For more information, see [Refactor param binding failure handling in RequestDelegate (`dotnet/aspnetcore` #64539)](https://github.com/dotnet/aspnetcore/pull/64539). (Please don't comment on closed issues and PRs.)
:::moniker-end
## Register a filter on controller actions
Rationale: Filters in Minimal API apps is the article a reader is in when they wonder why their filter sees empty arguments — the parameter-binding article explains the 400 but says nothing about filters, and the reverse is true here. Placing the content immediately after the filter-factory section is deliberate: the pass-through-factory case described at L120 is exactly the configuration the .NET 11 fix changed.
✏️ 2. Update — aspnetcore/fundamentals/minimal-apis/min-api-filters.md, ThrowOnBadRequest guidance
Applies to: >= aspnetcore-8.0
Location: Included in the insertion for change 1 as the "Observe binding failures in the Development environment" section.
Tracked as its own numbered change because it's separable: an author who decides the binding-failure section is out of scope should still add the ThrowOnBadRequest guidance, since that option is the single most valuable thing in the release note and has no occurrence anywhere in aspnetcore/ outside aspnetcore/release-notes/.
Rationale: Verified against RouteHandlerOptions.cs — the default really is IsDevelopment(), expressed as a <remarks> on the property rather than a field initializer, which is why the release note's "already the default in non-Development environments" is correct.
✏️ 3. Update — aspnetcore/fundamentals/minimal-apis/min-api-filters.md, .NET 11 pass-through factory note
Applies to: >= aspnetcore-11.0
Location: Included in the insertion for change 1 as the second moniker zone.
Rationale: This is the only part of the sub-area that is genuinely new in .NET 11, and it's the part the release note doesn't describe. Keeping it in a separate >= aspnetcore-11.0 zone means readers on 8.0–10.0 get the capability documentation without a claim that doesn't apply to them.
✏️ 4. Update — aspnetcore/fundamentals/minimal-apis/includes/parameter-binding8-10.md, insert after line 431
Applies to: >= aspnetcore-8.0 — the existing zone at L24–L494 already matches, so insert directly with no split.
Location: Line 431, immediately after the last row of the binding-failure table.
Before (lines 421–433):
## Binding failures
When binding fails, the framework logs a debug message and returns various status codes to the client depending on the failure mode.
|Failure mode|Nullable Parameter Type|Binding Source|Status code|
|--|--|--|--|
|`{ParameterType}.TryParse` returns `false` |yes|route/query/header|400|
|`{ParameterType}.BindAsync` returns `null` |yes|custom|400|
|`{ParameterType}.BindAsync` throws |doesn't matter|custom|500|
| Failure to deserialize JSON body |doesn't matter|body|400|
| Wrong content type (not `application/json`) |doesn't matter|body|415|
## Binding Precedence
After:
## Binding failures
When binding fails, the framework logs a debug message and returns various status codes to the client depending on the failure mode.
|Failure mode|Nullable Parameter Type|Binding Source|Status code|
|--|--|--|--|
|`{ParameterType}.TryParse` returns `false` |yes|route/query/header|400|
|`{ParameterType}.BindAsync` returns `null` |yes|custom|400|
|`{ParameterType}.BindAsync` throws |doesn't matter|custom|500|
| Failure to deserialize JSON body |doesn't matter|body|400|
| Wrong content type (not `application/json`) |doesn't matter|body|415|
Endpoints that have a filter pipeline run their filters after a binding failure, so a filter can observe the status code in the preceding table and replace the response body. For more information, see [Filters and parameter binding failures](xref:fundamentals/minimal-apis/min-api-filters#filters-and-parameter-binding-failures).
## Binding Precedence
Rationale: A cross-reference rather than duplicated prose. This table is where a reader lands when they search for the 400, and one sentence is enough to route them to the filter article without splitting the maintenance of the explanation across two files.
✅ 5. Update — TOC
No TOC change required. Both target articles already have TOC entries, and no new article is proposed.
✅ Action plan
- Confirm the coverage status summary, and in particular confirm the framing correction: elements 1–4 are not new in .NET 11 and must not be written as though they are. If the docs team prefers to present the whole thing as a .NET 11 item to match the release note, that decision should be made explicitly rather than by default.
- Apply change 4 first. It's a single sentence inside an existing, correctly scoped moniker zone and carries no zone risk.
- Apply changes 1–3 as the single insertion shown, then verify the two new zones are balanced:
min-api-filters.mdgoes from zero:::monikerdirectives to exactly two openers and two:::moniker-endmarkers. - Verify
<xref:Microsoft.AspNetCore.Routing.RouteHandlerOptions.ThrowOnBadRequest>,<xref:Microsoft.AspNetCore.Http.BadHttpRequestException>, and<xref:Microsoft.Extensions.Hosting.HostEnvironmentEnvExtensions.IsDevelopment%2A>all resolve. IfThrowOnBadRequestdoesn't resolve yet, fall back to inline code formatting and leave a<!-- TODO -->comment, matching the pattern already used in the release-note includes. - Build and confirm the new sections appear under the .NET 8, 9, 10, and 11 selectors, that the
>= aspnetcore-11.0note appears only under .NET 11, and that neither appears under .NET 7. - Confirm the new
#filters-and-parameter-binding-failuresanchor resolves from the include in change 4. - Resolve any OpenPublishing.Build warnings.
⚠️ Review considerations
- 🟣 Is the release note's framing intentional? The note presents a .NET 8 capability as a .NET 11 feature and credits @marcominerva, whose actual contribution (
#64539, commit003b66ce8a) changed three lines to swapfactoryContext.EndpointBuilder.FilterFactories.Count > 0forfilterPipelineBuilt. Someone on the product team should confirm whether the note is describing the capability deliberately (for discoverability) or whether it's an error. This report assumes the former and documents both. - 🟣 Does the source-generated Minimal API path (RDG) behave identically? The verified code path is
RequestDelegateFactory.#64539also touchedRequestDelegateCreationTests.Filters.cs, which suggests parity, but the generator's emitted code wasn't read. If the interpreted and source-generated paths differ, the new section needs a qualifier. - Out of scope for this issue: validation-produced 400s. The
Microsoft.Extensions.Validationendpoint filter is a separate mechanism with its own article, covered by other sub-areas of the same release-note section. - Not proposed: an edit to
aspnetcore/release-notes/aspnetcore-11/includes/endpoint-filters-binding-failures.md. The release note is out of scope as an edit target for this analysis, but if the framing above is confirmed as an error, correcting it there is worth a separate issue.
🔗 References
- What's New section: Endpoint filters observe parameter-binding failures
- Product source — the binding-failure branch that keeps the pipeline alive:
RequestDelegateFactory.csL994–L1013 - Product source — the
>= 400guard that suppresses the handler:RequestDelegateFactory.csL481–L486 - Product source — the pass-through-factory early return:
RequestDelegateFactory.csL501–L508 - Product source —
ThrowOnBadRequestand itsIsDevelopment()default:RouteHandlerOptions.cs - Implementing PR (.NET 11 fix): Refactor param binding failure handling in RequestDelegate (
dotnet/aspnetcore#64539), commit003b66ce8a - Originating PR (.NET 8 capability): Endpoint filter pipeline now returns EmptyHttpResult in case of 400 BadRequest (
dotnet/aspnetcore#45073), commitf69643b785
- Lenguaje dominante
- C#
- Estrellas
- 13.1k
- Forks
- 24.6k
- Merge medio
- 1 d 8 h
- PR fusionados (30 d)
- 117
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de dotnet/AspNetCore.Docs
-
v11 update: TLS channel binding token access from ITlsConnectionFeaturePosiblemente ocupada @guardrex la tomó hace 11 días. Abierto11.0 fundamentals/subsvc security/subsvc
Dificultad 2/5 1-3 horas Aptitud para principiantes 90/100
dotnet/AspNetCore.Docs#37726 · 1 reacción · 2 asignados ·
Los mantenedores suelen responder en 1 día
-
v11 update: Kestrel applies trailer header timeoutsPosiblemente ocupada @guardrex la tomó hace 7 días. Abierto11.0 fundamentals/subsvc
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
dotnet/AspNetCore.Docs#37725 · 1 reacción · 2 asignados ·
Los mantenedores suelen responder en 1 día
-
v11 update: Accurate rate-limiting Retry-After headersPosiblemente ocupada @guardrex la tomó hace 7 días. Abierto11.0 performance/subsvc
Dificultad 2/5 1-3 horas Aptitud para principiantes 86/100
dotnet/AspNetCore.Docs#37724 · 1 reacción · 2 asignados ·
Los mantenedores suelen responder en 1 día
-
v11 update: TLS handshake observability in KestrelPosiblemente ocupada @guardrex la tomó hace 4 días. Abierto11.0 fundamentals/subsvc
Dificultad 2/5 1-3 horas Aptitud para principiantes 92/100
dotnet/AspNetCore.Docs#37722 · 1 reacción · 2 asignados ·
Los mantenedores suelen responder en 1 día
-
v11 update: Native OpenTelemetry tracing for ASP.NET CorePosiblemente ocupada @guardrex la tomó hace 4 días. Abierto11.0 fundamentals/subsvc
Dificultad 1/5 1-3 horas Aptitud para principiantes 92/100
dotnet/AspNetCore.Docs#37721 · 1 reacción · 2 asignados ·
Los mantenedores suelen responder en 1 día
Todos los issues de dotnet/AspNetCore.Docs
Issues similares
-
[i18n] 安装实例完成后的成功提示未正确本地化Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 62/100
PCL-Community/PCL-CE#3658 ·
Los mantenedores suelen responder en 1 día
-
Deploy & Patch-issues opprettes ikke: create-pnd-issues.yml har feilet hver uke siden 2025-09-08Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 62/100
Altinn/altinn-auth#4359 ·
Los mantenedores suelen responder en 1 día
-
アプリ: チャット 優先: 中 提案
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
yksr-melt/Meltype#243 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Workflows: a workflow stored with null conditions is skipped with an exception instead of runAbiertobug core
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
Los mantenedores suelen responder en 1 día
-
type/automation type/tech-debt
Dificultad 1/5 Menos de una hora Aptitud para principiantes 72/100
Los mantenedores suelen responder en 1 día