Aspnetcore: System.ObjectDisposedException: Cannot write to the response body, the response has completed

Created on 28 Aug 2020  Β·  23Comments  Β·  Source: dotnet/aspnetcore

Application Name: Blazor-Blogs
OS: Windows 10 19H1
CPU: X64
.NET Build Number: 5.0.100-rc.1.20425.5-win-x64
App Location: On repro machine, at C:\Users\Appcompat\Documents\Blazor-Blogs
App Source: On repro machine, at C:\Users\Appcompat\Documents\Blazor-BlogsSource
App Source on GitHub link: https://github.com/ADefWebserver/Blazor-Blogs (this source is new, we test old version )

Verify Scenarios:
1). Windows 10 19H1 X64 + .NET Core SDK build 5.0.100-rc.1.20425.5-win-x64: Fail
2). Windows 10 19H1 X64 + .NET Core SDK build 5.0.100-preview.8.20417.9-win-x64 5.0 Preview 8: Pass
3). Windows 10 19H1 X64 + .NET Core SDK build 3.1.401-win-x64 Pass

Repro Machine:
See in https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1181306

Repo Machine Environment Info :
.NET SDK (reflecting any global.json):
Version: 5.0.100-rc.1.20425.5​
Commit: 85c205f9d8​

Runtime Environment:​
OS Name: Windows​
OS Version: 10.0.18362​
OS Platform: Windows​
RID: win10-x64​
Base Path: C:\Program Files\dotnet\sdk\5.0.100-rc.1.20425.5\​
​
Host (useful for support):​
Version: 5.0.0-rc.1.20425.1​
Commit: f4e99f4afa​
​
.NET SDKs installed:​
5.0.100-rc.1.20425.5 [C:\Program Files\dotnet\sdk]​
​
.NET runtimes installed:​
Microsoft.AspNetCore.App 5.0.0-rc.1.20424.9 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]​
Microsoft.NETCore.App 5.0.0-rc.1.20425.1 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]​
Microsoft.WindowsDesktop.App 5.0.0-rc.1.20424.1 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Repro steps:
On the repo machine
1) On CMD, type β€œset ASPNETCORE_ENVIRONMENT=Development”, then type β€œEnter”.
2) Type "cd C:\Users\Appcompat\Documents\Blazor-Blogs\Publish", then type β€œEnter”.
3) Type β€œdotnet BlazorBlogs.dll”,then type β€œEnter”.
4) Open the website: http://localhost:5000/ in Edge browser.

Expected Result:
Open website successfully

Actual Result:
An unhandled exception occurred while processing the request.
MissingFieldException: Field not found: 'Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame.FrameType'.

Exception Log:

MissingFieldException: Field not found: 'Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame.FrameType'.
Toolbelt.Blazor.HeadElement.Title.OnParametersSetAsync()​
System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start<TStateMachine>(ref TStateMachine stateMachine)​
System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start<TStateMachine>(ref TStateMachine stateMachine)​
Toolbelt.Blazor.HeadElement.Title.OnParametersSetAsync()​
Microsoft.AspNetCore.Components.ComponentBase.CallOnParametersSetAsync()​
Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()​
Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception)​
Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToPendingTasks(Task task)​
Microsoft.AspNetCore.Components.Rendering.ComponentState.SetDirectParameters(ParameterView parameters)​
Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewComponentFrame(ref DiffContext diffContext, int frameIndex)​
Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewSubtree(ref DiffContext diffContext, int frameIndex)​
Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InsertNewFrame(ref DiffContext diffContext, int newFrameIndex)​
Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.AppendDiffEntriesForRange(ref DiffContext diffContext, int oldStartIndex, int oldEndIndexExcl, int newStartIndex, int newEndIndexExcl)​
Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.ComputeDiff(Renderer renderer, RenderBatchBuilder batchBuilder, int componentId, ArrayRange<RenderTreeFrame> oldTree, ArrayRange<RenderTreeFrame> newTree)​
Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment)​
Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderInExistingBatch(RenderQueueEntry renderQueueEntry)​
Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()​
Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception)​
Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()​
Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessPendingRender()​
Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToRenderQueue(int componentId, RenderFragment renderFragment)​
Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged()​
Microsoft.AspNetCore.Components.ComponentBase.CallOnParametersSetAsync()​
Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()​
Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception)​
Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToPendingTasks(Task task)​
Microsoft.AspNetCore.Components.Rendering.ComponentState.SetDirectParameters(ParameterView parameters)​
Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderRootComponentAsync(int componentId, ParameterView initialParameters)​
Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.CreateInitialRenderAsync(Type componentType, ParameterView initialParameters)​
Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.RenderComponentAsync(Type componentType, ParameterView initialParameters)​
Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext+<>c__11<TResult>+<<InvokeAsync>b__11_0>d.MoveNext()​
Microsoft.AspNetCore.Mvc.ViewFeatures.StaticComponentRenderer.PrerenderComponentAsync(ParameterView parameters, HttpContext httpContext, Type componentType)​
Microsoft.AspNetCore.Mvc.ViewFeatures.ComponentRenderer.PrerenderedServerComponentAsync(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection)​
Microsoft.AspNetCore.Mvc.ViewFeatures.ComponentRenderer.RenderComponentAsync(ViewContext viewContext, Type componentType, RenderMode renderMode, object parameters)​
Microsoft.AspNetCore.Mvc.TagHelpers.ComponentTagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output)​
Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.<RunAsync>g__Awaited|0_0(Task task, TagHelperExecutionContext executionContext, int i, int count)​
BlazorBlogs.Pages.Pages__Host.<ExecuteAsync>b__14_1()​
Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.SetOutputContentAsync()​
BlazorBlogs.Pages.Pages__Host.ExecuteAsync()​
Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)​
Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, bool invokeViewStarts)​
Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)​
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, string contentType, Nullable<int> statusCode)​
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, string contentType, Nullable<int> statusCode)​
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_0<TFilter, TFilterAsync>(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)​
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)​
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext<TFilter, TFilterAsync>(ref State next, ref Scope scope, ref object state, ref bool isCompleted)​
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()​
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)​
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)​
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)​
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()​
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)​
Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)​
WilderMinds.MetaWeblog.MetaWeblogMiddleware.Invoke(HttpContext context, MetaWeblogService service)​
Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)​
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)​
Toolbelt.Blazor.HeadElement.Middlewares.HeadElementServerPrerenderingMiddleware.InvokeAsync(HttpContext context, IHeadElementHelperStore store)​
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.MigrationsEndPointMiddleware.Invoke(HttpContext context)​
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)​
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)​
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context).

Findings :
When we check the 'Microsoft.AspNetCore.Components.dll' version 5.0.0, we found the field in RenderTree class is "FrameTypeField".

[FieldOffset(4)]
internal RenderTreeFrameType FrameTypeField; //changed here

But in previous version, it is FrameType.

DUMP file captured and stored in repo machine, path for the DUMP file is C:\Users\Appcompat\Documents\Blazor-Blogs.DMP
Exception from the sample code above :

System.MissingFieldException: Field not found: 'Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame.FrameType'.
   at Toolbelt.Blazor.HeadElement.Title.OnParametersSetAsync()​
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)​
   at Toolbelt.Blazor.HeadElement.Title.OnParametersSetAsync()​
   at Microsoft.AspNetCore.Components.ComponentBase.CallOnParametersSetAsync()​
   at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()​
   at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception)​
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToPendingTasks(Task task)​
   at Microsoft.AspNetCore.Components.Rendering.ComponentState.SetDirectParameters(ParameterView parameters)​
   at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewComponentFrame(DiffContext& diffContext, Int32 frameIndex)​
   at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewSubtree(DiffContext& diffContext, Int32 frameIndex)​
   at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InsertNewFrame(DiffContext& diffContext, Int32 newFrameIndex)​
   at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.AppendDiffEntriesForRange(DiffContext& diffContext, Int32 oldStartIndex, Int32 oldEndIndexExcl, Int32 newStartIndex, Int32 newEndIndexExcl)​
   at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.ComputeDiff(Renderer renderer, RenderBatchBuilder batchBuilder, Int32 componentId, ArrayRange`1 oldTree, ArrayRange`1 newTree)​
   at Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment)​
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderInExistingBatch(RenderQueueEntry renderQueueEntry)​
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()​
--- End of stack trace from previous location ---​
   at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception)​
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()​
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessPendingRender()​
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToRenderQueue(Int32 componentId, RenderFragment renderFragment)​
   at Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged()​
   at Microsoft.AspNetCore.Components.ComponentBase.CallOnParametersSetAsync()​
   at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()​
   at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception)​
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToPendingTasks(Task task)​
   at Microsoft.AspNetCore.Components.Rendering.ComponentState.SetDirectParameters(ParameterView parameters)​
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderRootComponentAsync(Int32 componentId, ParameterView initialParameters)​
   at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.CreateInitialRenderAsync(Type componentType, ParameterView initialParameters)​
   at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.RenderComponentAsync(Type componentType, ParameterView initialParameters)​
   at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.<>c__11`1.<<InvokeAsync>b__11_0>d.MoveNext()

cc @dotnet-actwx-bot

area-servers bug

Most helpful comment

@jiangzeng01 I published the fixed version of the Toolbelt.Blazor.HeadElement.ServerPrerendering NuGet package.

Could you try it out?

@jsakamoto Thanks, we will have a try on this.

All 23 comments

Thanks for contacting us. This was an intentional binary-breaking change we've made. You will have to recompile your app to get things working. We will post an announcement about this shortly.

//cc @SteveSandersonMS

@mkArtakMSFT Thanks! Please update us with the announcement link when it's ready.

Thanks for contacting us. This was an intentional binary-breaking change we've made. You will have to recompile your app to get things working. We will post an announcement about this shortly.

//cc @SteveSandersonMS

@mkArtakMSFT We tried to re-compile with SDK 5.0.100-rc.1.20430.2, it does not work.
Do you have any specific steps to re-compile?

Ah, looking more closely at the stack trace, I see the error comes from Toolbelt.Blazor.HeadElement. This library also needs to be recompiled to work with .NET 5.

I've filed issue https://github.com/jsakamoto/Toolbelt.Blazor.HeadElement/issues/9 describing how the library needs to update. Until then it won't work with .NET 5 RC1 or later.

@SteveSandersonMS Thank you for your reporting to my Toolbelt.Blazor.HeadElement repository!

@jiangzeng01 I published the fixed version of the Toolbelt.Blazor.HeadElement NuGet package.

Could you try it out?

@SteveSandersonMS Thank you for your reporting to my Toolbelt.Blazor.HeadElement repository!

@jiangzeng01 I published the fixed version of the Toolbelt.Blazor.HeadElement NuGet package.

Could you try it out?

@jsakamoto We tried with the new package you provided and we will need to retarget to .NET 5 to make the Blazor-Blogs app work.

But another error we found in log while we try to launch the app, the app got launched and UI looks fine.
The launch issue should go away, do you know if this error is related with the new package?

C:\Users\Appcompat\Documents\blazorblognet5>BlazorBlogs.exe
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\Users\Appcompat\Documents\blazorblognet5
fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HM2JNNEUBS1D", Request id "0HM2JNNEUBS1D:00000001": An unhandled exception was thrown by the application.
      System.ObjectDisposedException: Cannot write to the response body, the response has completed.
      Object name: 'HttpResponseStream'.
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponsePipeWriter.<ValidateState>g__ThrowObjectDisposedException|14_0()
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseStream.FlushAsync(CancellationToken cancellationToken)
         **at Toolbelt.Blazor.HeadElement.Middlewares.FilterStream.FlushAsync(CancellationToken cancellationToken)**
         at Microsoft.AspNetCore.Http.StreamResponseBodyFeature.StartAsync(CancellationToken cancellationToken)
         at Microsoft.AspNetCore.Http.StreamResponseBodyFeature.CompleteAsync()
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.<FireOnCompleted>g__ProcessEvents|227_0(HttpProtocol protocol, Stack 1 events)
fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HM2JNNEUBS1D", Request id "0HM2JNNEUBS1D:00000003": An unhandled exception was thrown by the application.
      **System.ObjectDisposedException: Cannot write to the response body, the response has completed.**
      Object name: 'HttpResponseStream'.
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponsePipeWriter.<ValidateState>g__ThrowObjectDisposedException|14_0()
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseStream.FlushAsync(CancellationToken cancellationToken)
         at Toolbelt.Blazor.HeadElement.Middlewares.FilterStream.DefaultFlushAsyncInvoker(CancellationToken cancellationToken)
         at Toolbelt.Blazor.HeadElement.Middlewares.FilterStream.FlushAsync(CancellationToken cancellationToken)
         at Microsoft.AspNetCore.Http.StreamResponseBodyFeature.StartAsync(CancellationToken cancellationToken)
         at Microsoft.AspNetCore.Http.StreamResponseBodyFeature.CompleteAsync()
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.<FireOnCompleted>g__ProcessEvents|227_0(HttpProtocol protocol, Stack 1 events)
fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HM2JNNEUBS1F", Request id "0HM2JNNEUBS1F:00000002": An unhandled exception was thrown by the application.
      **System.InvalidOperationException: Cannot write to response body after connection has been upgraded.**
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.ThrowingWasUpgradedWriteOnlyStream.Flush()
         at System.IO.Stream.<>c.<FlushAsync>b__39_0(Object state)
         at System.Threading.Tasks.Task.InnerInvoke()
         at System.Threading.Tasks.Task.<>c.<.cctor>b__277_0(Object obj)
         at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
      --- End of stack trace from previous location ---
         at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
         at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
      --- End of stack trace from previous location ---
         at Microsoft.AspNetCore.Http.StreamResponseBodyFeature.CompleteAsync()
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.<FireOnCompleted>g__ProcessEvents|227_0(HttpProtocol protocol, Stack 1 events)

Ah, looking more closely at the stack trace, I see the error comes from Toolbelt.Blazor.HeadElement. This library also needs to be recompiled to work with .NET 5.

I've filed issue jsakamoto/Toolbelt.Blazor.HeadElement#9 describing how the library needs to update. Until then it won't work with .NET 5 RC1 or later.

@SteveSandersonMS We tried the app with new package of Toolbelt.Blazor.HeadElement, and re-compiled the app with latest RC1 SDK(the default target for the app is 3.1), it does not work.
Then we tried to target .NET 5 with the app, it works fine at launch, but another error found, see the comment above.
Do you think it is expected that we need to target .net5 for the app as well to make it work?
As you have mentioned above, we just need to re-compile with new SDK to make things work, not retarget .net5.

@jiangzeng01 I'm afraid I don't know about the Cannot write to the response body error. It looks like this is happening quite a bit lower in the stack. @halter73 Do you know if we changed anything in 5.0 that would affect this code and lead to a new ObjectDisposedException?

@jiangzeng01 I'm afraid I don't know about the _Cannot write to the response body_ error. It looks like this is happening quite a bit lower in the stack. @halter73 Do you know if we changed anything in 5.0 that would affect this code and lead to a new ObjectDisposedException?

@SteveSandersonMS Thanks, do you have update about following:
Do you think it is expected that we need to target .net5 then re-compile to make it work?
As you have mentioned above, we just need to re-compile with new SDK to make things work, not retarget .net5.

I have no idea to resolve this error at this time, but I succeed that reproduce that error.

The Toolbelt.Blazor.HeadElement.ServerPrerendering library replace HttpContext.Response.Body to another stream object for rewrite the contents of response.
This implementation looks like the reason for that error.

I wrote a minimal ASP.NET Core project that can reproduce the error. (See attached zip file.)

Do dotnet run on this minimal ASP.NET Core app project and send a "GET /foo/bar" request to this ASP.NET Core app to cause "HTTP 404 not found".
After doing that, the app will report the error System.ObjectDisposedException: Cannot write to the response body, the response has completed..

In the case that the request will respond to HTTP status 200 (such as "GET /index.html"), the app doesn't report any errors.
I think the error will happen when the HTTP status is not 202 (such as 304 Not Modified, 404 Not Found, etc.).

This minimal ASP.NET Core app just does that replace the HttpContext.Request.Body stream to another stream that passes through its operations to the original stream.

This app works fine on .NET SDK 5.0.100-preview.8.20417.9, but doesn't work well on .NET SDK 5.0.100-rc.1.20454.5 as reported above.

Anybody can investigate this behavior by rewriting global.json.

I hope this information is helpful to resolve this issue.

dotnet5aspnetfilterbody.zip

I see the issue, but I don't know why it would have changed in RC1, most of this behavior has been around since 3.0.

FilterStream replaces Response.Body on they way in.
https://github.com/jsakamoto/Toolbelt.Blazor.HeadElement/blob/c38e260c1434cd1297975191d300b8e81d67e12d/ServerPrerendering/Middlewares/FilterStream.cs#L50-L52
Here's what that does internally:
https://github.com/dotnet/aspnetcore/blob/3d042ac9aaf6df4cfd1d2bd4a984a86a45629285/src/Http/Http/src/Internal/DefaultHttpResponse.cs#L85-L87

The problem is that HeadElementServerPrerenderingMiddleware never reverts the change to the response body on the way out, so the default cleanup logic tries to run and hits the ODE. The fix is for HeadElementServerPrerenderingMiddleware to revert the response body which will suppress the default cleanup logic. https://github.com/jsakamoto/Toolbelt.Blazor.HeadElement/blob/c38e260c1434cd1297975191d300b8e81d67e12d/ServerPrerendering/Middlewares/HeadElementServerPrerenderingMiddleware.cs#L27-L29
https://github.com/dotnet/aspnetcore/blob/3d042ac9aaf6df4cfd1d2bd4a984a86a45629285/src/Http/Http/src/Internal/DefaultHttpResponse.cs#L78-L82

Side note. FilterStream.Dispose should not call OriginalStream.Dispose, it doesn't own that resource, the server does.
https://github.com/jsakamoto/Toolbelt.Blazor.HeadElement/blob/c38e260c1434cd1297975191d300b8e81d67e12d/ServerPrerendering/Middlewares/FilterStream.cs#L107

      Object name: 'HttpResponseStream'.
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponsePipeWriter.<ValidateState>g__ThrowObjectDisposedException|14_0()
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseStream.FlushAsync(CancellationToken cancellationToken)
         at Toolbelt.Blazor.HeadElement.Middlewares.FilterStream.DefaultFlushAsyncInvoker(CancellationToken cancellationToken)
         at Toolbelt.Blazor.HeadElement.Middlewares.FilterStream.FlushAsync(CancellationToken cancellationToken)
         at Microsoft.AspNetCore.Http.StreamResponseBodyFeature.StartAsync(CancellationToken cancellationToken)
         at Microsoft.AspNetCore.Http.StreamResponseBodyFeature.CompleteAsync()
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.<FireOnCompleted>g__ProcessEvents|227_0(HttpProtocol protocol, Stack 1 events)

The problem is that a Response.OnCompleted() callback is invoking StreamResponseBodyFeature.CompleteAsync() which eventually calls Kestrel's HttpResponseStream.FlushAsync(). The problem is by the time Response.OnCompleted() callbacks fire, the request and response body streams have already been disposed.

All access to the request and response body streams should be completed before middleware completes. Response.OnCompleted() is called after middleware completes.

I'm not sure why StreamResponseBodyFeature.CompleteAsync/StartAsync() needs to flush the inner stream. In ASP.NET Core, the response stream always auto-flushes unless it's been wrapped by some there's some buffering stream wrapping it. What would be buffered but unflushed at the start of the response anyway?

https://github.com/dotnet/aspnetcore/blob/3d042ac9aaf6df4cfd1d2bd4a984a86a45629285/src/Http/Http/src/StreamResponseBodyFeature.cs#L104-L110

Ah, this was an RC1 change: https://github.com/dotnet/aspnetcore/pull/24058. We can look into that, but HeadElementServerPrerenderingMiddleware should still revert the response body change and that would avoid the issue.

So, is this a blocking issue for RC1? What scenarios do we think it will cause problems in?

This only affects middleware that doesn't reset the response body, and it's just log noise because an "app-defined" (really DefaultHttpResponse-defined) callback threw an exception.

I talked to @Tratcher and we agree we can make changes to StreamResponseBodyFeature to make it more defensive in this case, but it's not a big deal for RC1.

Here's something we could do to be more defensive. It's a new method on StreamResponseBodyFeature to be registered with OnCompleted instead of CompleteAsync. It's mostly the same as CompleteAsync but it doesn't call StartAsync. StartAsync doesn't make much sense from the OnCompleted callback, but it can matter when someone calls CompleteAsync directly.

        internal async Task DisposeAsync()
        {
            // DisposeAsync is registered with HttpResponse.OnCompleted and there's no way to unregister it.
            // Prevent it from running by marking as disposed.
            if (_disposed)
            {
                return;
            }
            if (_completed)
            {
                return;
            }

            _disposed = true;
            _completed = true;

            if (_pipeWriter != null)
            {
                await _pipeWriter.CompleteAsync();
            }
        }

@Tratcher

Side note. FilterStream.Dispose should not call OriginalStream.Dispose, it doesn't own that resource, the server does.

Thank you that let me know it!
I'll fix it.

HeadElementServerPrerenderingMiddleware should still revert the response body change and that would avoid the issue.

Thanks for the advice.

I fixed this point at Toolbelt.Blazor.HeadElement commit 4603fc5d, and it looks works fine including my E2E test.

I'll repackage it and publish it to nuget.org as soon as possible.

@jiangzeng01 I published the fixed version of the Toolbelt.Blazor.HeadElement.ServerPrerendering NuGet package.

Could you try it out?

On the subject of the original binary-breaking change in RenderTreeFrame in the RC1 release, an announcement is now posted at https://github.com/aspnet/Announcements/issues/438.

@jiangzeng01 I published the fixed version of the Toolbelt.Blazor.HeadElement.ServerPrerendering NuGet package.

Could you try it out?

@jsakamoto Thanks, we will have a try on this.

On the subject of the original binary-breaking change in RenderTreeFrame in the RC1 release, an announcement is now posted at aspnet/Announcements#438.

@jsakamoto The app works fine with these 2 new packages, thanks a lot for fixing.

Was this page helpful?
0 / 5 - 0 ratings