-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Refactor param binding failure handling in RequestDelegate #64539
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Refactor `RequestDelegateFactory` to handle parameter binding failures more flexibly: - Execute filter pipelines on failure if filters are present and return type is `ValueTask<object?>`, allowing filters to observe/modify the 400 response. - Short-circuit with a 400 response if no filters are present. - Simplify logic using `Expression.Condition` and `Expression.Block`. Add new test cases in `RouteHandlerEndpointRouteBuilderExtensionsTest.cs`: - Validate behavior with/without filters and custom responses. - Ensure handlers are not executed on binding failure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request refactors parameter binding failure handling in RequestDelegateFactory to provide more flexible filter integration. The key change allows endpoint filters to observe and potentially customize 400 Bad Request responses when parameter binding fails, rather than always short-circuiting before filters run.
Key Changes:
- Unified the parameter binding failure logic using
Expression.Conditioninstead of separate code paths for filtered vs non-filtered scenarios - When filters are present and return type is
ValueTask<object?>, the filter pipeline executes on binding failure, allowing filters to customize the 400 response - Added comprehensive test coverage for binding failure scenarios with and without filters
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/Http/Http.Extensions/src/RequestDelegateFactory.cs | Refactored CreateParamCheckingResponseWritingMethodCall to conditionally run filter pipelines on parameter binding failures, simplifying the code with a unified Expression.Condition approach |
| src/Http/Routing/test/UnitTests/Builder/RouteHandlerEndpointRouteBuilderExtensionsTest.cs | Added 4 test cases validating parameter binding failure behavior: with/without filters, with custom filter responses, and with validation filters |
| [Fact] | ||
| public async Task ParameterBindingFailure_WithAddValidation_DoesNotExecuteHandler() | ||
| { | ||
| // Arrange | ||
| var services = new ServiceCollection().AddSingleton(LoggerFactory); | ||
| services.AddValidation(); // Register validation filter factory | ||
| var serviceProvider = services.BuildServiceProvider(); | ||
|
|
||
| var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(serviceProvider)); | ||
| var handlerExecuted = false; | ||
|
|
||
| // Act - Binding failure happens BEFORE validation filter runs | ||
| builder.MapGet("/test/{id}", (Guid id) => | ||
| { | ||
| handlerExecuted = true; | ||
| return 1; | ||
| }); | ||
|
|
||
| var dataSource = GetBuilderEndpointDataSource(builder); | ||
| var endpoint = Assert.Single(dataSource.Endpoints); | ||
|
|
||
| var httpContext = new DefaultHttpContext | ||
| { | ||
| RequestServices = serviceProvider | ||
| }; | ||
| httpContext.Request.RouteValues["id"] = "invalid-guid"; | ||
|
|
||
| // Act | ||
| await endpoint.RequestDelegate!(httpContext); | ||
|
|
||
| // Assert | ||
| Assert.Equal(StatusCodes.Status400BadRequest, httpContext.Response.StatusCode); | ||
| Assert.True(httpContext.Response.Body.Length == 0, "Response body should be empty"); | ||
| Assert.False(handlerExecuted, "Handler should not have been executed when parameter binding fails"); | ||
| } | ||
|
|
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The AddValidation() extension method requires a reference to Microsoft.Extensions.Validation, but this test project (Microsoft.AspNetCore.Routing.Tests.csproj) doesn't appear to have that reference. This will cause a compilation error.
Either:
- Add a reference to
Microsoft.Extensions.Validationin the project file, or - Move this test to the
Microsoft.AspNetCore.Http.Extensions.Testsproject where validation testing is more appropriate, or - Remove this test if validation integration testing is not needed here
| [Fact] | |
| public async Task ParameterBindingFailure_WithAddValidation_DoesNotExecuteHandler() | |
| { | |
| // Arrange | |
| var services = new ServiceCollection().AddSingleton(LoggerFactory); | |
| services.AddValidation(); // Register validation filter factory | |
| var serviceProvider = services.BuildServiceProvider(); | |
| var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(serviceProvider)); | |
| var handlerExecuted = false; | |
| // Act - Binding failure happens BEFORE validation filter runs | |
| builder.MapGet("/test/{id}", (Guid id) => | |
| { | |
| handlerExecuted = true; | |
| return 1; | |
| }); | |
| var dataSource = GetBuilderEndpointDataSource(builder); | |
| var endpoint = Assert.Single(dataSource.Endpoints); | |
| var httpContext = new DefaultHttpContext | |
| { | |
| RequestServices = serviceProvider | |
| }; | |
| httpContext.Request.RouteValues["id"] = "invalid-guid"; | |
| // Act | |
| await endpoint.RequestDelegate!(httpContext); | |
| // Assert | |
| Assert.Equal(StatusCodes.Status400BadRequest, httpContext.Response.StatusCode); | |
| Assert.True(httpContext.Response.Body.Length == 0, "Response body should be empty"); | |
| Assert.False(handlerExecuted, "Handler should not have been executed when parameter binding fails"); | |
| } | |
| // [Fact] | |
| // public async Task ParameterBindingFailure_WithAddValidation_DoesNotExecuteHandler() | |
| // { | |
| // // Arrange | |
| // var services = new ServiceCollection().AddSingleton(LoggerFactory); | |
| // services.AddValidation(); // Register validation filter factory | |
| // var serviceProvider = services.BuildServiceProvider(); | |
| // | |
| // var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(serviceProvider)); | |
| // var handlerExecuted = false; | |
| // | |
| // // Act - Binding failure happens BEFORE validation filter runs | |
| // builder.MapGet("/test/{id}", (Guid id) => | |
| // { | |
| // handlerExecuted = true; | |
| // return 1; | |
| // }); | |
| // | |
| // var dataSource = GetBuilderEndpointDataSource(builder); | |
| // var endpoint = Assert.Single(dataSource.Endpoints); | |
| // | |
| // var httpContext = new DefaultHttpContext | |
| // { | |
| // RequestServices = serviceProvider | |
| // }; | |
| // httpContext.Request.RouteValues["id"] = "invalid-guid"; | |
| // | |
| // // Act | |
| // await endpoint.RequestDelegate!(httpContext); | |
| // | |
| // // Assert | |
| // Assert.Equal(StatusCodes.Status400BadRequest, httpContext.Response.StatusCode); | |
| // Assert.True(httpContext.Response.Body.Length == 0, "Response body should be empty"); | |
| // Assert.False(handlerExecuted, "Handler should not have been executed when parameter binding fails"); | |
| // } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All the tests succeeded, so there are no compilation errors at all. In particular, this test focuses on behavior related to parameter-binding failures, so I think it makes sense to keep it here.
Description
Refactor
RequestDelegateFactoryto handle parameter binding failures more flexibly:ValueTask<object?>, allowing filters to observe/modify the 400 response.Expression.ConditionandExpression.Block.Add new test cases in
RouteHandlerEndpointRouteBuilderExtensionsTest.cs:Fixes #64341