Skip to content

Commit b6000f5

Browse files
authored
Throw for bad requests intended for minimal route handlers in development (dotnet#36004)
1 parent 5fdf28d commit b6000f5

25 files changed

+3284
-2359
lines changed

src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,6 +1264,53 @@ public async Task HostingStartupRunsWhenApplicationIsNotEntryPointApplicationNam
12641264
Assert.Equal("value", app.Configuration["testhostingstartup:config"]);
12651265
}
12661266

1267+
[Fact]
1268+
public async Task DeveloperExceptionPageWritesBadRequestDetailsToResponseByDefaltInDevelopment()
1269+
{
1270+
var builder = WebApplication.CreateBuilder(new WebApplicationOptions() { EnvironmentName = Environments.Development });
1271+
builder.WebHost.UseTestServer();
1272+
await using var app = builder.Build();
1273+
1274+
app.MapGet("/{parameterName}", (int parameterName) => { });
1275+
1276+
await app.StartAsync();
1277+
1278+
var client = app.GetTestClient();
1279+
1280+
var response = await client.GetAsync("/notAnInt");
1281+
1282+
Assert.False(response.IsSuccessStatusCode);
1283+
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
1284+
Assert.Contains("text/plain", response.Content.Headers.ContentType.MediaType);
1285+
1286+
var responseBody = await response.Content.ReadAsStringAsync();
1287+
Assert.Contains("parameterName", responseBody);
1288+
Assert.Contains("notAnInt", responseBody);
1289+
}
1290+
1291+
[Fact]
1292+
public async Task NoExceptionAreThrownForBadRequestsInProduction()
1293+
{
1294+
var builder = WebApplication.CreateBuilder(new WebApplicationOptions() { EnvironmentName = Environments.Production });
1295+
builder.WebHost.UseTestServer();
1296+
await using var app = builder.Build();
1297+
1298+
app.MapGet("/{parameterName}", (int parameterName) => { });
1299+
1300+
await app.StartAsync();
1301+
1302+
var client = app.GetTestClient();
1303+
1304+
var response = await client.GetAsync("/notAnInt");
1305+
1306+
Assert.False(response.IsSuccessStatusCode);
1307+
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
1308+
Assert.Null(response.Content.Headers.ContentType);
1309+
1310+
var responseBody = await response.Content.ReadAsStringAsync();
1311+
Assert.Equal(string.Empty, responseBody);
1312+
}
1313+
12671314
class ThrowingStartupFilter : IStartupFilter
12681315
{
12691316
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)

src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<Compile Include="$(SharedSourceRoot)ProblemDetailsJsonConverter.cs" LinkBase="Shared"/>
1818
<Compile Include="$(SharedSourceRoot)HttpValidationProblemDetailsJsonConverter.cs" LinkBase="Shared" />
1919
<Compile Include="$(SharedSourceRoot)RoutingMetadata\AcceptsMetadata.cs" LinkBase="Shared" />
20+
<Compile Include="$(SharedSourceRoot)TypeNameHelper/TypeNameHelper.cs" LinkBase="Shared"/>
2021
</ItemGroup>
2122

2223
<ItemGroup>

src/Http/Http.Extensions/src/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions.RouteParameterNames.get
163163
Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions.RouteParameterNames.init -> void
164164
Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions.ServiceProvider.get -> System.IServiceProvider?
165165
Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions.ServiceProvider.init -> void
166+
Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions.ThrowOnBadRequest.get -> bool
167+
Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions.ThrowOnBadRequest.init -> void
166168
Microsoft.AspNetCore.Mvc.ProblemDetails
167169
Microsoft.AspNetCore.Mvc.ProblemDetails.Detail.get -> string?
168170
Microsoft.AspNetCore.Mvc.ProblemDetails.Detail.set -> void

src/Http/Http.Extensions/src/RequestDelegateFactory.cs

Lines changed: 101 additions & 67 deletions
Large diffs are not rendered by default.

src/Http/Http.Extensions/src/RequestDelegateFactoryOptions.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using Microsoft.Extensions.Logging;
5+
46
namespace Microsoft.AspNetCore.Http
57
{
68
/// <summary>
7-
/// Options for controlling the behavior of <see cref="RequestDelegate" /> when created using <see cref="RequestDelegateFactory" />.
9+
/// Options for controlling the behavior of the <see cref="RequestDelegate" /> when created using <see cref="RequestDelegateFactory" />.
810
/// </summary>
911
public sealed class RequestDelegateFactoryOptions
1012
{
@@ -17,5 +19,11 @@ public sealed class RequestDelegateFactoryOptions
1719
/// The list of route parameter names that are specified for this handler.
1820
/// </summary>
1921
public IEnumerable<string>? RouteParameterNames { get; init; }
22+
23+
/// <summary>
24+
/// Controls whether the <see cref="RequestDelegate"/> should throw a <see cref="BadHttpRequestException"/> in addition to
25+
/// writing a <see cref="LogLevel.Debug"/> log when handling invalid requests.
26+
/// </summary>
27+
public bool ThrowOnBadRequest { get; init; }
2028
}
2129
}

0 commit comments

Comments
 (0)