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) + { + + } + } }