From 3eba680c09d78d36b94606f85f1238780a539cdd Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 18 Dec 2022 19:14:17 -0800 Subject: [PATCH] Don't set the status code if the response has started - Middleware that writes then ends up at the terminal middleware ends up throwing an exception. This change doesn't set the status code if the response has started. - Added a test --- .../Http/src/Builder/ApplicationBuilder.cs | 5 +- src/Http/Http/test/ApplicationBuilderTests.cs | 46 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/Http/Http/src/Builder/ApplicationBuilder.cs b/src/Http/Http/src/Builder/ApplicationBuilder.cs index 53181969640d..3297e2f76f23 100644 --- a/src/Http/Http/src/Builder/ApplicationBuilder.cs +++ b/src/Http/Http/src/Builder/ApplicationBuilder.cs @@ -132,7 +132,10 @@ public RequestDelegate Build() throw new InvalidOperationException(message); } - context.Response.StatusCode = StatusCodes.Status404NotFound; + if (!context.Response.HasStarted) + { + context.Response.StatusCode = StatusCodes.Status404NotFound; + } return Task.CompletedTask; }; diff --git a/src/Http/Http/test/ApplicationBuilderTests.cs b/src/Http/Http/test/ApplicationBuilderTests.cs index fa66214499c5..3211e3b0344d 100644 --- a/src/Http/Http/test/ApplicationBuilderTests.cs +++ b/src/Http/Http/test/ApplicationBuilderTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.Builder.Internal; @@ -19,6 +20,23 @@ public void BuildReturnsCallableDelegate() Assert.Equal(404, httpContext.Response.StatusCode); } + [Fact] + public async Task BuildReturnDelegateThatDoesNotSetStatusCodeIfResponseHasStarted() + { + var builder = new ApplicationBuilder(null); + var app = builder.Build(); + + var httpContext = new DefaultHttpContext(); + var responseFeature = new TestHttpResponseFeature(); + httpContext.Features.Set(responseFeature); + httpContext.Response.StatusCode = 200; + + responseFeature.HasStarted = true; + + await app.Invoke(httpContext); + Assert.Equal(200, httpContext.Response.StatusCode); + } + [Fact] public void ServerFeaturesEmptyWhenNotSpecified() { @@ -96,4 +114,32 @@ public void PropertiesDictionaryIsDistinctAfterNew() Assert.Equal("value1", builder1.Properties["test"]); } + + private class TestHttpResponseFeature : IHttpResponseFeature + { + private int _statusCode = 200; + public int StatusCode + { + get => _statusCode; + set + { + _statusCode = HasStarted ? throw new NotSupportedException("The response has already started") : value; + } + } + public string ReasonPhrase { get; set; } + public IHeaderDictionary Headers { get; set; } + public Stream Body { get; set; } = Stream.Null; + + public bool HasStarted { get; set; } + + public void OnCompleted(Func callback, object state) + { + + } + + public void OnStarting(Func callback, object state) + { + + } + } }