Open primozcerar opened 2 years ago
@primozcerar thanks for contacting us.
Can you create a minimal repro project as a public github repository that reproduces the issue?
Hi @primozcerar. We have added the "Needs: Author Feedback" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.
Not easily or quickly. I was hoping the stack trace would be enough to track down the issue. I have looked at the source code and I suspect the problem is here: https://github.com/dotnet/aspnetcore/blob/c85baf8db0c72ae8e68643029d514b2e737c9fae/src/Mvc/Mvc.ViewFeatures/src/Buffers/ViewBuffer.cs#L258
I can not be sure of course.
Thank you for filing this issue. In order for us to investigate this issue, please provide a minimalistic repro project (ideally a GitHub repo) that illustrates the problem.
Hi @primozcerar. We have added the "Needs: Author Feedback" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.
Unfortunately I am unable to reproduce the error in a simple project. There must be some combination of settings or view hierarchy that causes this. I still believe the problem is in calling the synchronous write from the ViewBuffer WriteToAsync method as is visible in the call stack. I realize IHtmlContent does not provide a WriteAsync method but then it should be handled differently.
@primozcerar do you happen to have any @FlushAsync()
call on your page?
It's very strange, as MVC will buffer all the response by default.
Hi @primozcerar. We have added the "Needs: Author Feedback" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.
No @FlushAsync() in the project.
All I can add is that I use Devexpress controls extensively in the project and possibly there is something in their code that would lead to this issue, but the root problem seen in the call stack still remains. I also did not see any reports in their support ticket system about this problem.
@primozcerar thanks for the additional details.
It might help if you can figure out what specific ViewComponent causes the issue to begin with (what VC creates that content). That said, unfortunately, without a minimal repro is impossible for us to make progress on this issue.
Hi @primozcerar. We have added the "Needs: Author Feedback" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.
Hi! Hopefully I'm not hijacking this issue, but I ran into what looks like the same thing this morning on a project that was recently upgraded from .NET 5.0 to 6.0 *.
The original code is something we inherited so it may very well be doing something ill-advised, but it was working before and isn't now, so here I am! 😃
A very basic repro should be visible in this repo: https://github.com/welshronaldo/ViewCompTest
That's just a default new web project with a ViewComponent used in a similar manner to what we've got in the real project. The component in this case just returns a select tag with "N" GUIDs, specified in the numerical input (the real one is loading things from a database, inevitably). It's fine up to 163 items, but breaks with the same exception detailed above at 164. For the sake of the repro, I'm just dumping that raw response into the DOM.
The issue from our perspective is easily worked around in the meantime, and we probably need to reassess what we're doing in our specific case, but hopefully this sheds some light anyway.
Thanks for your time!
*Edit: After further review, the .NET version upgrade itself is unrelated and just coincided with the content in the component having grown in the intervening time. I'm able to reproduce the behavior (on a repro like the one linked) as far back as netcoreapp3.1.
Just following up with the apparent solution for our specific case:
Replacing the old-style:
@Html.DropDownList("listGuids", Model, new { @class = "form-control" })
with:
<select asp-items="Model" id="listGuids" class="form-control"></select>
... resolves the issue. No idea if that's related to what you're dealing with @primozcerar, but best of luck with it!
@welshronaldo Thank you for this. I appreciate it since I couldn't reproduce the issue in a simple project. Hopefully this helps solve it.
Edit: Your repro project seems to show the same issue judging from the call stack of the exception. I even tried to change the TestComponent.cs in your project to use InvokeAsync just in case that changes anything but the exception remains.
Thanks for contacting us.
We're moving this issue to the .NET 7 Planning
milestone for future evaluation / consideration. Because it's not immediately obvious that this is a bug in our framework, we would like to keep this around to collect more feedback, which can later help us determine the impact of it. We will re-evaluate this issue, during our next planning meeting(s).
If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues.
To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.
I don't understand how you can say that it is not immediately obvious that this is a bug in asp.net core? The project welshronaldo provided uses nothing but asp.net core framework so the bug can not be from somewhere else. All calls are made async yet the exception is thrown. I understand the need to prioritize but this is quite obviously a bug.
@primozcerar the comment our bot made might not be completely accurate in this case. This is simply part of our planning process, we are acknowledging that there is very likely an issue here. However an engineer from our team needs to spend time looking at it to understand the details and produce a detailed explanation of what's going on before we decide whether this is a bug, a bad design or a technical limitation and then decide what next steps we should take.
I think I'm hitting exactly the same issue. Invoking a VC directly from a Controller action method using
return ViewComponent(...
crashes with error
System.InvalidOperationException: Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead. at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseStream.Write(Byte[] buffer, Int32 offset, Int32 count) at Microsoft.AspNetCore.WebUtilities.HttpResponseStreamWriter.FlushInternal(Boolean flushEncoder) at Microsoft.AspNetCore.WebUtilities.HttpResponseStreamWriter.Write(String value)
It works fine most of the time, but in one specific case the view component returns html containing a large json object - in this case data for a Kendo dropdown list which contains nearly 1000 items.
I'm sure there are work-rounds for this - e.g. enabling synchronous IO, but that's not the point.
I've come across this issue too.
I've managed to create a simple reproduction. It has to do with the length of content returned.
namespace WebApplication1.Controllers;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc;
public class HomeController : Controller
{
public IActionResult Index()
{
return this.ViewComponent(typeof(WorkingViewComponent));
}
public IActionResult Fail()
{
return this.ViewComponent(typeof(FailingViewComponent));
}
}
public class WorkingViewComponent : ViewComponent
{
public IHtmlContent Invoke()
{
return new HtmlString(new String('X', 15000));
}
}
public class FailingViewComponent : ViewComponent
{
public IHtmlContent Invoke()
{
return new HtmlString(new String('X', 20000));
}
}
Version: 6.0.302 Commit: c857713418
ping @javiercn
We face the same problem too.
I've managed to create a simple reproduction. It has to do with the length of content returned.
That's right, It's related to the length of the view content.
@TanayParikh can you please investigate this and summarize your findings and any actions to be taken for resolving this issue? Thanks!
I followed the reproduction provided by @psylenced then found the threshold is 16K
.
public class FailingViewComponent : ViewComponent
{
public IHtmlContent Invoke()
{
return new HtmlString(new string('X', 16385)); // 16384 is OK
}
}
I searched 16 * 1024
and found the DefaultBufferSize
in MemoryPoolHttpResponseStreamWriterFactory.cs#L28
public const int DefaultBufferSize = 16 * 1024;
I tried to implement CustomMemoryPoolHttpResponseStreamWriterFactory
to change the DefaultBufferSize
to 32K
.
public class CustomMemoryPoolHttpResponseStreamWriterFactory : IHttpResponseStreamWriterFactory
{
public const int DefaultBufferSize = 32 * 1024;
private readonly ArrayPool<byte> _bytePool;
private readonly ArrayPool<char> _charPool;
public CustomMemoryPoolHttpResponseStreamWriterFactory(
ArrayPool<byte> bytePool,
ArrayPool<char> charPool)
{
_bytePool = bytePool;
_charPool = charPool;
}
public TextWriter CreateWriter(Stream stream, Encoding encoding)
{
return new HttpResponseStreamWriter(stream, encoding, DefaultBufferSize, _bytePool, _charPool);
}
}
Replacing MemoryPoolHttpResponseStreamWriterFactory
with CustomMemoryPoolHttpResponseStreamWriterFactory
.
builder.Services.TryAddSingleton<IHttpResponseStreamWriterFactory, CustomMemoryPoolHttpResponseStreamWriterFactory>();
It worked as exptected.
// FailingViewComponent no longer failed
public class FailingViewComponent : ViewComponent
{
public IHtmlContent Invoke()
{
// 16385 is OK with CustomMemoryPoolHttpResponseStreamWriterFactory
return new HtmlString(new string('X', 16385));
}
}
That is not a solution in my opinion. It should be async but it isn't.
It works with async.
public class HomeController : Controller
{
public IActionResult Fail()
{
return ViewComponent(typeof(FailingViewComponent));
}
}
// FailingViewComponent no longer failed
public class FailingViewComponent : ViewComponent
{
public Task<IHtmlContent> InvokeAsync()
{
// 16385 is OK with CustomMemoryPoolHttpResponseStreamWriterFactory
return Task.FromResult<IHtmlContent>(new HtmlString(new string('X', 16385)));
}
}
And does it then accept more than the buffer size without error? More than 32kB in your case.
No. The DefaultBufferSize
is the limit.
And that is the problem that needs to be solved.
ViewBuffer.cs#L98 could result in sync WriteTo
. The EncodingWrapper
implements IHtmlContent
, see ViewBuffer.cs#L388
AppendValue(new ViewBufferValue(new EncodingWrapper(unencoded)));
if (value.Value is string valueAsString)
{
await writer.WriteAsync(valueAsString);
continue;
}
if (value.Value is ViewBuffer valueAsViewBuffer)
{
await valueAsViewBuffer.WriteToAsync(writer, encoder);
continue;
}
if (value.Value is IHtmlContent valueAsHtmlContent)
{
valueAsHtmlContent.WriteTo(writer, encoder);
await writer.FlushAsync();
continue;
}
value.Value
is ViewBufferValue.Value
I believe I'm also encountering this issue as well.
I'm attempting to use ASP.NET Core 8 Preview 5 with the new Blazor full-stack / SSR. I have a page that just has static HTML in it (a privacy page.) Formatted HTML is about 460 lines. I get this error. If I lower the lines to around 240 the page loads showing about 16kb data from this page.
Is this the same issue? Is there a work around?
Yep, I have a Blazor 8 (preview 6) page with static HTML and if there's "too much" text, I get the error. Reducing the text size fixes it. It's not even that much text! How do I get Blazor to work? this is a very simple app that Blazor should easily be able to support. @mkArtakMSFT @danroth27
@kjbetz @MisinformedDNA I have ran into this issue as well with Blazor .NET 8 preview 6. If the body contains too much text, the page won't load and it throws an error. I created a separate issue for this here (before I saw your comments): https://github.com/dotnet/aspnetcore/issues/49466
I'm having the same issue invoking a big view component. I have some sync view components inside it as well but even turning them async doesn't do the trick.
Edit: I'm using a asp.net core web app
It seems some work has been done regarding this issue on the Blazor side #49172 . Will this be ported to asp.net core?
Just wanted to add my name to the list. Same issue but with a partial in Asp.Net core razor pages.
Recently I encountered the same problem. I've a ViewComponent
that's returning a big HtmlString
which is coming from the DB.
The good news is that .NET 8 introduced the IHtmlAsyncContent
interface for Blazor in this #47117 pull request.
So if you just want to return a big HtmlString
from a ViewComponent
you can fix it this way:
public class HtmlAsyncString : HtmlString, IHtmlAsyncContent
{
public HtmlAsyncString(string? value) : base(value) { }
public async ValueTask WriteToAsync(TextWriter writer)
{
ArgumentNullException.ThrowIfNull(writer);
await writer.WriteAsync(Value);
}
}
public class FailedBeforeViewComponent : ViewComponent
{
public IHtmlAsyncContent Invoke()
{
return new HtmlAsyncString(new string('X', 20000)); // Does not fail anymore
}
}
// Also compatible with IHtmlContent
public class FailedBeforeViewComponent : ViewComponent
{
public IHtmlContent Invoke()
{
return new HtmlAsyncString(new string('X', 20000)); // Does not fail anymore
}
}
// async example:
public class FailedBeforeViewComponent : ViewComponent
{
public async Task<IHtmlContent> InvokeAsync()
{
await Task.Delay(1); // Just a demo
return new HtmlAsyncString(new string('X', 20000)); // Does not fail anymore
}
}
I ran into the same issue converting an Asp.NET Core 2.2 application to Asp.Net Core 8.0. All view components are called via InvokeAsync, I am still getting a synchronous write in HttpResponseStream on some components, same as shown in the original stack trace.
So if this is not a bug, then what is the proper way to code this, and if there is no proper way, then how is this not declared a bug yet?
Hi, I just hit this issue today and this is 100% a bug in ASP.NET. I'm sorry if this post contains redundant information but I've tried to investigate what is going on.
Like many have discovered the problem arises when returning a ViewComponentResult
from a controller rendering a component that uses some IHtmlContent
like HtmlString
and the length is over 16384 bytes.
This is happening because ViewComponentResultExecutor
renders components using DefaultViewComponentHelper
which in turn is using an instance of the ViewBuffer
class which writes synchronously to a HttpResponseStreamWriter
with a 16384 byte buffer. When the buffer overflows data is flushed and this is when the exception is thrown.
ViewBuffer
calls the synchronous method WriteTo
on IHtmlContent
(unless the class implements IHtmlAsyncContent
):
https://github.com/dotnet/aspnetcore/blob/3f8edf130a14d34024a42ebd678fa3f699ef5916/src/Mvc/Mvc.ViewFeatures/src/Buffers/ViewBuffer.cs#L242-L247
The TextWriter
that is written to is created here:
https://github.com/dotnet/aspnetcore/blob/3f8edf130a14d34024a42ebd678fa3f699ef5916/src/Mvc/Mvc.ViewFeatures/src/ViewComponentResultExecutor.cs#L101
The factory is a MemoryPoolHttpResponseStreamWriterFactory
which initializes HttpResponseStreamWriter
instances with a buffer size of 16384 bytes.
https://github.com/dotnet/aspnetcore/blob/3f8edf130a14d34024a42ebd678fa3f699ef5916/src/Mvc/Mvc.Core/src/Infrastructure/MemoryPoolHttpResponseStreamWriterFactory.cs#L28
Flushes happen in many of the Write
-methods in HttpResponseStreamWriter
.
https://github.com/dotnet/aspnetcore/blob/3f8edf130a14d34024a42ebd678fa3f699ef5916/src/Http/WebUtilities/src/HttpResponseStreamWriter.cs
To work around this problem I have implemented a buffering result executor using recyclable memory streams that should only affect ViewComponentResult
in particular.
public class BufferingViewComponentResultExecutor(
IOptions<MvcViewOptions> mvcHelperOptions,
ILoggerFactory loggerFactory,
HtmlEncoder htmlEncoder,
IModelMetadataProvider modelMetadataProvider,
ITempDataDictionaryFactory tempDataDictionaryFactory,
IHttpResponseStreamWriterFactory writerFactory
)
: ViewComponentResultExecutor(mvcHelperOptions, loggerFactory, htmlEncoder, modelMetadataProvider, tempDataDictionaryFactory, writerFactory)
{
public override async Task ExecuteAsync(ActionContext context, ViewComponentResult result)
{
var originalBody = context.HttpContext.Response.Body;
try
{
await using var memoryStream = SomeMemoryStreamManager.Instance.GetStream(nameof(BufferingViewComponentResultExecutor));
// Alternative: using var memoryStream = new MemoryStream();
context.HttpContext.Response.Body = memoryStream;
await base.ExecuteAsync(context, result);
memoryStream.Seek(0, SeekOrigin.Begin);
await memoryStream.CopyToAsync(originalBody);
}
finally
{
context.HttpContext.Response.Body = originalBody;
}
}
}
// Then register it like this after AddMvc
services.AddSingleton<IActionResultExecutor<ViewComponentResult>, BufferingViewComponentResultExecutor>();
Is there an existing issue for this?
Describe the bug
Controller.ViewComponent writes synchronously to the HttpResponseStream if the content of the component is large enough causing the "System.InvalidOperationException: 'Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true "
Calling the same view from the controller with Controller.PartialView and the path to the component view or creating a wrapper view that calls @await Component.InvokeAsync avoids the issue. There are no synchronous renderpartial or partial calls in the view. The response size is 25kB.
This is the call stack of the exception:
Example code with error:
No error:
No error 2:
Expected Behavior
As visible from the call stack ViewBuffer.WriteToAsync calls the Stream.Write method instead of Stream.WriteAsync. Why this only happens by calling Controller.ViewComponent I am not sure.
Steps To Reproduce
No response
Exceptions (if any)
System.InvalidOperationException: 'Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true
.NET Version
.net6
Anything else?
.NET SDK (reflecting any global.json): Version: 6.0.101 Commit: ef49f6213a
Runtime Environment: OS Name: Windows OS Version: 10.0.19043 OS Platform: Windows RID: win10-x64 Base Path: C:\Program Files\dotnet\sdk\6.0.101\
Host (useful for support): Version: 6.0.1 Commit: 3a25a7f1cc
.NET SDKs installed: 2.1.201 [C:\Program Files\dotnet\sdk] 2.1.202 [C:\Program Files\dotnet\sdk] 2.1.300 [C:\Program Files\dotnet\sdk] 2.1.401 [C:\Program Files\dotnet\sdk] 2.1.402 [C:\Program Files\dotnet\sdk] 2.1.526 [C:\Program Files\dotnet\sdk] 2.1.818 [C:\Program Files\dotnet\sdk] 2.2.103 [C:\Program Files\dotnet\sdk] 2.2.401 [C:\Program Files\dotnet\sdk] 2.2.402 [C:\Program Files\dotnet\sdk] 3.1.417 [C:\Program Files\dotnet\sdk] 5.0.104 [C:\Program Files\dotnet\sdk] 5.0.404 [C:\Program Files\dotnet\sdk] 5.0.406 [C:\Program Files\dotnet\sdk] 6.0.101 [C:\Program Files\dotnet\sdk]
.NET runtimes installed: Microsoft.AspNetCore.All 2.1.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.2.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.2.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.App 2.1.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.2.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.2.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.1.22 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.1.23 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 5.0.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 5.0.15 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 6.0.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 2.0.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.0.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.3-servicing-26724-03 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.2.1 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.2.8 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.1.22 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.1.23 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 5.0.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 5.0.15 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 6.0.1 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.WindowsDesktop.App 3.1.22 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 3.1.23 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 5.0.13 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 5.0.15 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 6.0.1 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]