Skip to content

Commit c1a234d

Browse files
author
John Luo
authored
Check for HasStarted after running ExceptionHandler (#32198)
1 parent e92aeff commit c1a234d

File tree

2 files changed

+69
-1
lines changed

2 files changed

+69
-1
lines changed

src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerMiddleware.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,8 @@ private async Task HandleException(HttpContext context, ExceptionDispatchInfo ed
132132

133133
await _options.ExceptionHandler!(context);
134134

135-
if (context.Response.StatusCode != StatusCodes.Status404NotFound || _options.AllowStatusCode404Response)
135+
// If the response has already started, assume exception handler was successful.
136+
if (context.Response.HasStarted || context.Response.StatusCode != StatusCodes.Status404NotFound || _options.AllowStatusCode404Response)
136137
{
137138
if (_diagnosticListener.IsEnabled() && _diagnosticListener.IsEnabled("Microsoft.AspNetCore.Diagnostics.HandledException"))
138139
{

src/Middleware/Diagnostics/test/UnitTests/ExceptionHandlerTest.cs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,73 @@ public async Task DoesNotModifyCacheHeaders_WhenNoExceptionIsThrown()
312312
}
313313
}
314314

315+
[Fact]
316+
public async Task ExceptionHandlerSucceeded_IfExceptionHandlerResponseHasStarted()
317+
{
318+
using var host = new HostBuilder()
319+
.ConfigureWebHost(webHostBuilder =>
320+
{
321+
webHostBuilder
322+
.UseTestServer()
323+
.Configure(app =>
324+
{
325+
app.Use(async (httpContext, next) =>
326+
{
327+
Exception exception = null;
328+
try
329+
{
330+
await next(httpContext);
331+
}
332+
catch (InvalidOperationException ex)
333+
{
334+
exception = ex;
335+
}
336+
337+
Assert.Null(exception);
338+
});
339+
340+
app.UseExceptionHandler("/handle-errors");
341+
342+
app.Map("/handle-errors", (innerAppBuilder) =>
343+
{
344+
innerAppBuilder.Run(async (httpContext) =>
345+
{
346+
httpContext.Response.StatusCode = StatusCodes.Status404NotFound;
347+
await httpContext.Response.WriteAsync("Custom 404");
348+
});
349+
});
350+
351+
app.Run(httpContext =>
352+
{
353+
httpContext.Response.Headers.Add("Cache-Control", new[] { "max-age=3600" });
354+
httpContext.Response.Headers.Add("Pragma", new[] { "max-age=3600" });
355+
httpContext.Response.Headers.Add("Expires", new[] { DateTime.UtcNow.AddDays(10).ToString("R") });
356+
httpContext.Response.Headers.Add("ETag", new[] { "abcdef" });
357+
358+
throw new InvalidOperationException("Something bad happened");
359+
});
360+
});
361+
}).Build();
362+
363+
await host.StartAsync();
364+
365+
using (var server = host.GetTestServer())
366+
{
367+
var client = server.CreateClient();
368+
var response = await client.GetAsync(string.Empty);
369+
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
370+
Assert.Equal("Custom 404", await response.Content.ReadAsStringAsync());
371+
IEnumerable<string> values;
372+
Assert.True(response.Headers.CacheControl.NoCache);
373+
Assert.True(response.Headers.CacheControl.NoStore);
374+
Assert.True(response.Headers.TryGetValues("Pragma", out values));
375+
Assert.Single(values);
376+
Assert.Equal("no-cache", values.First());
377+
Assert.False(response.Headers.TryGetValues("Expires", out _));
378+
Assert.False(response.Headers.TryGetValues("ETag", out _));
379+
}
380+
}
381+
315382
[Fact]
316383
public async Task DoesNotClearCacheHeaders_WhenResponseHasAlreadyStarted()
317384
{

0 commit comments

Comments
 (0)