dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.36k stars 9.99k forks source link

Blazor RenderModeAuto detect when WASM client is downloaded / available #51445

Open rudiv opened 1 year ago

rudiv commented 1 year ago

Is there an existing issue for this?

Is your feature request related to a problem? Please describe the problem.

No response

Describe the solution you'd like

I'm not certain if this is already available, looking around the docs and other issues in the repo I'm not sure it's even come up.

Given that RenderModeAuto only applies on the next visit or a refresh, it would be ideal if we could detect the state of background downloading of WASM assets.

This would allow us to present a form of UI to the user allowing them to refresh the page to get the WASM experience, whilst also freeing up server resources from holding the Server circuits open for longer than they necessarily have to be.

Of course, even better than this would be an API for download progress, so we could present an actual progress bar to the user, but that's really not necessary.

Additional context

This may not be a typical request, but in the nature of B2B SaaS like ours people tend to leave tabs open for a very long time. This could result in an unnecessarily long circuit being open.

Granted it's likely to be closed by the browser at some point, or on another deployment at our side (think CI) prompting the user to refresh anyway, but if we're able to do that in seconds (ie. when download is complete) rather than hours or potentially even days it could improve the experience and of course free up server resources.

mahald commented 1 year ago

If you're aiming to utilize WebAssembly (often referred to as WASM) in your application but you'd like users to see something (e.g., a loading spinner) while the WASM content is downloading, prerendering can be a great solution. Here's how you can achieve this:

@rendermode RenderMode.InteractiveWebAssembly

if (_data == null)
{
    <img src="LoadingSpinner.gif" alt="Loading..."> </img>
}
else
{
    // Your WASM page content goes here
}

@code {
    private List<YourDataType>? _data = null;

    protected override async Task OnInitializedAsync()
    {
        if (!OperatingSystem.IsBrowser())
        {
            return;
        }

        _data = GetDataSomeHowAsync();
    }
}

So, what happens here?

@rendermode RenderMode.InteractiveWebAssembly: This directive specifies the rendering mode for the component. RenderMode.InteractiveWebAssembly means the page is being prerendered server-side until the WASM app becomes interactive. This helps with the initial user experience.

Conditional Rendering: The page checks if _data is null. If it's null, this implies that the WebAssembly content hasn't loaded yet, and it shows a loading spinner image. Otherwise, when the WASM content has been loaded and _data is populated, it renders the actual content of the page.

During server-side prerendering, OperatingSystem.IsBrowser() returns false, resulting in only the spinner being displayed without any attempt to retrieve data.

After the WASM content is downloaded, the page is rendered prior to the OnInitializedAsync lifecycle method being invoked, again showing the spinner.

Once the OnInitializedAsync method completes its execution, the page is re-rendered, this time displaying the content from _data.

rudiv commented 1 year ago

Thanks @mahald, but that's not the intent here.

This is specifically referring to RenderModeAuto, where Server is used by default and the WASM is downloaded in the background however is not used until the next request (once it is fully downloaded and cached).

Being able to detect when the WASM has successfully downloaded would allow us to display some UI element that allows the user to refresh and switch to the WASM experience immediately, rather than waiting for them to arbitrarily refresh the page or come back later to close the circuit.

Your solution would be a great workaround for a "WASM only" app that's relatively small in size, however when the payload is approaching 10MB, they'd be sat with the loading spinner for a long while.

ghost commented 1 year ago

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

Andrzej-W commented 11 months ago

@rudiv It looks like a good idea, but not trivial to implement correctly. If you have some page which is server interactive initially it is possible that there is some state. Think about:

These are only a few examples. Try to write smart enough code to save this state somewhere and then restore when WASM application will start to make the switch invisible to the user. In my opinion it is almost impossible, especially when you use some nontrivial third party components.

rudiv commented 11 months ago

@Andrzej-W I agree with that aspect being particularly context, but my idea was not for the framework to automatically do "Oh, I've got WASM now, switch over!", more that we as developers have an API that allows us to detect when WASM is indeed available, and provide an application-specific prompt to the user to switch.

If we had this available, we (again referring to app devs, rather than framework) could certainly restrict the prompt to showing in "safe spaces" such as a Dashboard (which would have limited interactivity to be broken on the reload).

I would see any prompt we could implement doing nothing more than a window.location.reload() call.

Whilst it would be great to be invisible to the user, I don't think it's necessary at all. The potentially significant benefit to developers is that we can sever the Blazor Server circuit as soon as possible and free up server resources, whilst still having immediate interactivity available to new customers.

EDIT: Going further in the potential simplicity of this, implementors could even just poll a simple boolean. That's all we'd need?

function checkBlazorWasmState() {
    if (blazor.wasmAvailable) {
        someJSAccessibleState = true; // or DotNet.invokeMethodAsync(...)
    } else {
        setTimeout(checkBlazorWasmState, 5000);
    }
}
checkBlazorWasmState();
Andrzej-W commented 11 months ago

I think we can assume that even on a slow internet connection your WASM application will be downloaded within 30 seconds. You can reload the application, e.g. 60 seconds after launch, and it will run as WASM. If the only problem you want to solve is server load this solution should be enough for you. Am I right?

rudiv commented 11 months ago

Well sure, I could also vent noxious gas from an environment that normally takes 10 seconds, go in after 10 seconds, and die.

A little extreme of a metaphor perhaps, but I'd much rather wait for a green check / gas vented notice before going in. Just like I would want to wait for it to download before reloading, otherwise it's just recycling a circuit.

I'm assuming that something happens at the end of the background download of WASM has completed anyway, all I'm suggesting here is that it does one more thing to make that information available to the developer.

Andrzej-W commented 11 months ago

It looks that at the end of downloading process Blazor sets some value in local storage. Here is the screenshot (sorry, my browser is Polish). obraz You can poll this value from JavaScript code and reload on change. Maybe it is not perfect, but better than noxious gas.

On the other hand, if the internet connection between the user and your application is so bad that the WASM application won't finish loading after 1 minute, your application will probably be unusable in server mode as well.

rudiv commented 11 months ago

That definitely works, way better than the gas! 😃

If it's doing this then a "proper" API could probably be added for it quite easily, even if it's a pass thru method that checks this variable currently, but is resistant to change insofar as I assume this is technically an internal implementation detail?

It does bring me to another question though, what if the hash has changed and a new version is required/downloading? This is something else that a real API may need to consider.

ghost commented 10 months ago

Thanks for contacting us.

We're moving this issue to the .NET 9 Planning milestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. 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.

alex-fitplans commented 1 month ago

Subscribing to this.

I'm doing a B2C website and I'd like to auto-switch to WASM on my terms when the app knows that all assemblies have been downloaded. An example would be to trigger a full page reload when entering a highly interactive section, if assemblies have loaded. I can take care of forwarding the state to wasm myself, through url for ex.

This way on good scenarios most users would avoid blazor server potential issues (i.e. slow network lags / spotty connections).

All that's required is a JS event that says "all assemblies have downloaded". Or better, and event that fires for each assembly successful download, with the count of pending assemblies.