Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

v11 update: Endpoint filters observe parameter-binding failures

Abierto
#37,697 0 comentarios 0 reacciones 0 asignados Ver en GitHub

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

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

11.0 fundamentals/subsvc

[!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:

  1. 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 the StatusCode >= 400 guard 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.
  2. 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 returned next unchanged (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 named ParameterBindingFailure_WithEndpointFilterFactory_DoesNotExecuteHandler.
  3. The status-code guard is >= 400, not == 400. The release note tells readers to check HttpContext.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:

  1. aspnetcore/fundamentals/minimal-apis/min-api-filters.md never mentions that a filter runs when binding fails, or how to detect it.
  2. RouteHandlerOptions and ThrowOnBadRequest are undocumented in the docset, including the environment-dependent default.
  3. 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 == 400 and substitute their own response body.

In the Development environment, set RouteHandlerOptions.ThrowOnBadRequest = false so the framework returns a 400 that the filter can observe instead of throwing BadHttpRequestException to the developer exception page. This is already the default in non-Development environments.

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

  1. 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.
  2. Apply change 4 first. It's a single sentence inside an existing, correctly scoped moniker zone and carries no zone risk.
  3. Apply changes 1–3 as the single insertion shown, then verify the two new zones are balanced: min-api-filters.md goes from zero :::moniker directives to exactly two openers and two :::moniker-end markers.
  4. Verify <xref:Microsoft.AspNetCore.Routing.RouteHandlerOptions.ThrowOnBadRequest>, <xref:Microsoft.AspNetCore.Http.BadHttpRequestException>, and <xref:Microsoft.Extensions.Hosting.HostEnvironmentEnvExtensions.IsDevelopment%2A> all resolve. If ThrowOnBadRequest doesn't resolve yet, fall back to inline code formatting and leave a <!-- TODO --> comment, matching the pattern already used in the release-note includes.
  5. Build and confirm the new sections appear under the .NET 8, 9, 10, and 11 selectors, that the >= aspnetcore-11.0 note appears only under .NET 11, and that neither appears under .NET 7.
  6. Confirm the new #filters-and-parameter-binding-failures anchor resolves from the include in change 4.
  7. 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, commit 003b66ce8a) changed three lines to swap factoryContext.EndpointBuilder.FilterFactories.Count > 0 for filterPipelineBuilt. 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. #64539 also touched RequestDelegateCreationTests.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.Validation endpoint 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

Lenguaje dominante
C#
Estrellas
13.1k
Forks
24.6k
Merge medio
1 d 8 h
PR fusionados (30 d)
117

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de dotnet/AspNetCore.Docs

Todos los issues de dotnet/AspNetCore.Docs

Issues similares

Más issues de C#

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.