-
Notifications
You must be signed in to change notification settings - Fork 10.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Blazor] Stream response ends before components finish rendering #54157
Comments
Thanks for sharing your concerns, @sbwalker. |
@mkArtakMSFT the following repo contains a minimal repro: https://github.com/oqtane/OqtaneSSR Run the app and the Home page displays: The red square indicates where content is supposed to be rendered by the OqtaneSSR.Client\Components\Modules\Home.razor component
Now modify the OqtaneSSR.Client\Components\Root\Routes.razor component and include the StreamRendering attribute:
and the content will be displayed: no changes to the app other than adding StreamRendering... the "magic pill". |
After further testing, the answer to the title of this issue is that StreamRendering is NOT a magic pill - it IS a Future Headache. When used in a root component of an app, StreamRendering produces a "flash" effect as it progressively pushes content to the browser which can result in a poor user experience. |
@MackinnonBuck please let me know if you would like to discuss this issue further. |
@MackinnonBuck by "latest .NET 8 patch release" are you referring to 8.0.3? |
@MackinnonBuck if you are referring to 8.0.3, Oqtane is already running on 8.0.3 and the problem still exists. To be clear, this issue is NOT about StreamRendering... it is that Blazor refuses to render content in SSR for some unexplainable reason, and the only way to force content to be rendered is by adding the StreamRendering attribute: But by including StreamRendering on the Routes component it results in a UI "flash" for some pages, which is not desirable. |
@sbwalker, the fix I was referring to was introduced in 8.0.2, so it seems like that bug isn't the cause of what you're seeing. I briefly looked into the repro and on my machine, the "Hello StreamRendering" text displays even without stream rendering enabled. But I do see that the problematic code is called from an It's possible that adding Ultimately, I haven't been able to repro the exact behavior you're seeing so this is all a bit of a guess. I noticed that the repro uses configuration to decide render modes to use, so maybe there's a difference in local configuration that's resulting in the repro being different on my end. Could you provide a more minimal repro without all that machinery that demonstrates the issue you're seeing? Thanks. Edit: It would be great if the repro also didn't use |
@MackinnonBuck I am not sure how the code in the https://github.com/oqtane/OqtaneSSR repo could work for you? I just updated the references to 8.0.3 and tried running it locally: Then I uncomment the StreamRendering attribute in Routes.razor: And run the application and it displays the content: Your comment about the async void event handler is interesting. Specifically you are referring to this code in the custom SiteRouter:
This SiteRouter code was based on the default Blazor Router and that specific method in Oqtane has not changed since it is was introduced in 2018... but it could certainly still be a problem which has been exposed by the different run-time behavior in Static Blazor. In the original default Blazor Router it was simply a void method - but in Oqtane it was modified to be async because the Refresh method contains HttpClient calls (all of the routing configuration in Oqtane is stored in a database which is loaded dynamically at runtime - note that Oqtane was originally an Interactive Blazor application and still supports this render mode). In the latest Blazor Router the method has changed slightly to be "internal virtual" (with support for navigation interception):
So I think what you are suggesting is to try to find a way to avoid the async void method. Unfortunately async Task does not work because the NavigationManager.LocationChanged -= LocationChanged event handler does not like it. Which I believe is why it ended being specified as async void in the first place. Do you have a suggestion on how to declare the LocationChanged event handler in such a way that it would permit asynchronous calls within the implementation? The reason why the default Blazor Router does not suffer from this is because it relies on reflection and purely static configuration... but as soon as you try to embrace a more dynamic approach to routing, the asynchronous requirement becomes very important. I really appreciate your assistance. |
I did a fresh clean of the repo, pulled the latest commit, confirmed that stream rendering was disabled in Not sure what to say except the problem doesn't repro for me. The site uses static routing when I run it, so
You can use |
@MackinnonBuck I was very confused why this would not repro for you... and then I noticed that there was an appsettings.Development.json file in the repo - perhaps this config was being used when you were running the solution? I have removed the file so that it now uses the appsettings.json which contains the expected configuration. When running the app it should look like: And you are correct that when running on Static Rendering (the default for the app) the LocationChanged() method is not even called because this is only relevant in Interactive Rendering. So I don't think this is the source of the problem. However, I do think you are correct that the rendering problem is related to async calls. If you comment out the code in SiteRouter.razor in the Refresh method which is performing an HttpClient call (to simulate retrieving data from a backend API): The content will be displayed as expected (even with StreamRendering commented out in Routes.razor). Note that the Refresh method is actually running synchronously in this scenario because the Refresh method no longer contains an await call. Refresh is an async Task (not async void) and if f I now add an await Task.Yield() so that the method is asynchronous: The content is no longer displayed: But moving the await Task.Yield() to the end of the method: Allows the content to be displayed once again: (however in practice this would not work because in a real app the HttpClient service call is necessary to retrieve the values for setting the PageState object). To add a bit more context, the Oqtane SSR application is a highly simplified POC to demonstrate the core concepts of the Oqtane Framework (https://github.com/oqtane/oqtane.framework). The Oqtane Framework supports ALL Blazor render modes. It is multi-tenant and supports multiple "sites" from the same installation. Each "site' specifies its preferred Render Mode and Interactivity: This is all working... however for Static render mode it only works because there is a StreamRendering attribute set in Routes.razor. If the attribute is removed, a blank browser screen is rendered: However if I View Page Source in the browser, there is content: And in browser dev tools: I demonstrated this to Dan Roth at the MVP Summit. I even tried disabling Enhanced Navigation in the App.razor and it does not resolve the problem either… no content is displayed unless I include the [StreamRendering] attribute. So ultimately the purpose of the Oqtane SSR simplified POC repro project is to try to figure out why Blazor Static Server Rendering does not work - which will hopefully reveal the solution for the Oqtane Framework. |
Thanks for the additional details, @sbwalker. I cloned the latest version of your repro and ran it, and it still worked fine for me. Is the repro set up in a way where it can just be cloned and run without additional configuration? AFAICT, the conditions are set up correctly to reproduce the bug (stream rendering disabled, an async I even tried it on a fresh machine... still worked. Are you able to simulate a clean environment and run the project? If so, does the issue still happen after doing that? What I did was:
|
@MackinnonBuck I can’t say I have had a “it doesn’t work on my machine” moment for a long time, as .NET generally creates repeatable solutions. I will install Windows Sandbox and give it a try. I will also request the Oqtane community try to run the solution in the repo to see if they experience issues or not. At the end of the day it is not the issue in this repo, it is the Oqtane Framework rendering problem which I want to resolve… so I am now wondering if the Oqtane Framework can be run in the Windows Sandbox so that the real problem can be reproduced (blank browser screen). I will let you know. |
@MackinnonBuck the fact that you were not able to reproduce the problem prompted me to do some further investigation... I started the Oqtane migration to use the new .NET 8 Blazor approach (ie. blazor.web.js) in January. I was still running the original .NET 8.0.0 SDK at that time... and it exhibited the problems outlined in the earlier posts in this thread (ie. rendering a blank screen unless I added StreamRendering). Other Oqtane developers also experienced the same behavior - which is why I logged the issue. Later, the Oqtane solution was updated to newer versions of .NET 8 - ie. the project files were modified to reference 8.0.1, 8.0.2, 8.0.3. However, I just realized that I did not actually install the SDK for each of those versions on my local machine. So although I thought that I was running on the latest SDK, I actually was not. This is why I continued to see the rendering problems described in this issue. After you mentioned that you could not reproduce the problem, I installed the 8.0.3 SDK and the problem no longer exists. So it appears that one of the SDK patch releases resolved the problem. However, I could not be certain which specific SDK patch release contained the fix - which made me a bit uncomfortable as it is not always possible to control the SDK version which is available in a run-time environment. Case in point is Azure. I generally deploy Oqtane on Azure App Services, and the only configuration option you have is the ability to specify the major .NET version - so I have mine set to .NET 8. However, it is not clear what SDK version is installed so I used Kudu and ran dotnet --info. The results are: C:\home>dotnet --info So it appears Azure is running 8.0.1 (2 versions behind the latest 8.0.3). This indicated that Oqtane may or may not function properly in that environment - as I was only able to confirm that it works on 8.0.3. So I created a new custom deployment and tested it out, and was able to confirm that it works fine on 8.0.1. So this means that whatever problem which was causing the rendering issue was fixed in the 8.0.1 SDK patch release. Long story short... this issue can probably be closed, as the rendering problem is now resolved (the only remaining question which has not been addressed is if there is any official guidance which could be provided to developers in regards to when StreamRendering should be used - as without this guidance it will be used arbitrarily without understanding the consequences). I very much appreciated your assistance in resolving this issue. |
I'm glad you were able to find the cause of the issue, @sbwalker!
Is there something that our current guidance is lacking that you think could use additional clarification? I'm not aware of any scenarios where toggling stream rendering breaks the app in unintuitive ways (on the latest 8.0.3 SDK, that is). |
@MackinnonBuck the current guidance you linked to is quite thorough. The only scenario which is not mentioned is that enabling StreamRendering can cause unexpected "The renderer does not have a component with ID ##" in some scenarios where you are dynamically creating components. This seems to be caused by the differences in the initial content rendered in versus the final content which Blazor has a difficult time reconciling. I will need to create a simple repro for this and log it as a new issue. |
Spoke too soon... the Azure site where I deployed the bits with StreamRendering removed is still not rendering content in some scenarios: So I am guessing that SDK 8.0.1 did not fix this issue... perhaps it was fixed in SDK 8.0.2 or 8.0.3. Now I need to figure out how to upgrade the SDK in my Azure App Service. |
Upgraded my Azure App Service to .NET SDK 8.0.4 (using Development Tools / Extensions) and the rendering problem is resolved. |
Thank you so much @sbwalker , I was struggling to figure out, all day, why StreamRendering was working locally, but not on App Service. Added 8.0.4 to Extensions in App Service and now everything is working smoothly. Thanks for updating. |
Glad to hear that the upgrade fixed it!
A new issue with a repro would be great if you'd be willing to provide one! |
Updated the title so that others experiencing a similar issue can find this one more easily. |
Is there an existing issue for this?
Describe the bug
For the past 3 weeks I have been migrating a traditional Blazor application (https://github.com/oqtane/oqtane.framework) to the new Blazor approach in .NET 8. The process has not been smooth. The main problems are not related to the refactoring of the code - they are related to the run-time behavior of Blazor and the fact that it is a "black box".
More specifically, I am referring to scenarios where Blazor refuses to render a component... where it executes component logic such as StateHasChanged() and then does nothing - ie. it does not refresh the UI, it does not throw an exception, and it does not log anything in the browser console. Many days can be spent trying to track down these types of issues and in the end there is no resolution because it is not possible to identify the root cause (because the problem is occurring deep within the Blazor framework itself and there is no developer feedback loop).
Example # 1
An application which runs fine on global interactive rendering, attempts to run on static rendering. Within Visual Studio all of the logic is executing fine - no exceptions, no browser console messages - however the browser simply displays a blank page. If you view the source in the browser, you only see content from the App component - nothing else has been rendered. This resulted in 3-4 days of trying to identify the problem.
Example # 2
A stattically rendered component which indicates that a variable contains content within Visual Studio and should be rendered in the UI, however the UI is never updated with the content. This provoked another 3-4 days of investigation.
In both of these examples above (and a number of others) it turns out that there is a simple solution...
@attribute [StreamRendering]
Including this attribute within a component definition appears to be a "magic pill" to cure all sorts of strange, unexplainable rendering problems. Basically, rather than spending days trying to diagnose the Blazor "black box" I now sprinkle a component with some [StreamRendering] pixie dust, and it usually fixes the problem. From a developer productivity perspective alone, this attribute has already saved me from many days of spinning my wheels.
For Example # 1 including [StreamRendering] on the Routes component allowed the entire application to render as expected. For Example # 2 including [StreamRendering] on a custom Head component allowed page titles to be rendered as expected.
My concern is that most "magic pills" have side effects... they may solve the original problem but cause other issues in the process. In the case of [StreamRendering] there is very little documentation about what it is actually doing under the covers. Most of the information simply describes its purpose and benefits at a very high level. The fact that the Blazor Web template only includes it on the Weather component would seem to indicate that it should be used sparingly. So what are the implications from a performance, scalability, security perspective? Before I go ahead and sprinkle this attribute throughout my Blazor applications I would like to better understand the long term consequences.
Expected Behavior
Better guidance around the usage of [StreamRendering] in terms of when it should or should not be used, and the impacts of using it within an application.
Steps To Reproduce
No response
Exceptions (if any)
No response
.NET Version
8.0
Anything else?
No response
The text was updated successfully, but these errors were encountered: