Skip to content
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

Component will be prerendered (executed) in server with InteractiveWebAssembly settings. #51342

Closed
1 task done
nakamacchi opened this issue Oct 13, 2023 · 7 comments
Closed
1 task done
Labels
area-blazor Includes: Blazor, Razor Components

Comments

@nakamacchi
Copy link

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

[Symptoms]
When InteractiveWebAssembly is specified in the razor component created on the Client Project side, the initial processing is executed on the server side.

[Problems caused by this]
If HttpClient is @Inject on the WASM razor component, runtime will try to inject the server-side HttpClient on the first operation. An exception occurs when HttpClient is registered only on the WASM Client project side.

This behavior is reasonable if we use InteractiveAuto, but I suspect this is a bug when using InteractiveWebAssembly. (Maybe it is by-design, but it is very confusing.)

Expected Behavior

[Expected behavior]
When InteractiveWebAssembly is specified in the razor component created on the Client Project side, all processing is executed on the browser side.

Steps To Reproduce

No response

Exceptions (if any)

[Workaround]
It can be avoided by disabling prerendering by specifying below. However, the settings are very roundabout.

@rendermode InteractiveWebAssemblyWithoutPrerendering
@code {
static IComponentRenderMode InteractiveWebAssemblyWithoutPrerendering = new InteractiveWebAssemblyRenderMode(prerender: false);
}

or

@Attribute [RenderModeInteractiveWebAssembly(prerender: false)]
(I've heard that this is not deprecated in GA.

[Suggestion]

  • Create a non-prerendering option in the RendorMode enumeration (such like InteractiveWebAssemblyWithoutPrerendering)
  • It would be nice to have a way to disable prerendering globally

.NET Version

No response

Anything else?

No response

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-blazor Includes: Blazor, Razor Components label Oct 13, 2023
@augustevn
Copy link

augustevn commented Oct 13, 2023

  1. The Blazor pre-rendering setup has always worked liked that, Server does first serve and needs those same service registrations, middlewares etc.

  2. If you only want Blazor WASM without pre-rendering, you can remove the Server project or just start from the blazorwasm (Blazor WebAssembly Standalone App) or blazorwasm-empty templates instead of the blazor (Blazor Web App) template.

  • Just a hobbyist trying to help, not an official.

Some things are bit confusing at first indeed. Feel free to watch: https://www.youtube.com/@kis.stupid
I have some videos on Blazor pre-rendering, upgrading to .NET 8 etc.

@mahald
Copy link

mahald commented Oct 13, 2023

The prerenderer always runs on the Server. This may means you will need to add your "Service" for DepndecyInjection in the .Client for WebAssembly and in The Server for prerender.

You could disable prerender like this if you don't want it (then it will only run in Browser):

@rendermode _rendermode

......


@code 
{
            private static IComponentRenderMode _rendermode = new InteractiveWebAssemblyRenderMode(prerender: false);
} 

@mahald
Copy link

mahald commented Oct 13, 2023

PS works for all Render modes:
private static IComponentRenderMode _rendermode = new InteractiveWebAssemblyRenderMode(prerender: false);

private static IComponentRenderMode _rendermode = new InteractiveServerRenderMode(prerender: false);

private static IComponentRenderMode _rendermode = new InteractiveAutoRenderMode(prerender: false);

@mahald
Copy link

mahald commented Oct 14, 2023

Using pre-render mode can enhance the responsiveness of your site, so it might be beneficial to incorporate it.
One approach is to only inject the IServiceProvider and obtain other services manually.
By doing so, you can opt not to fetch data in OnInitializedAsync if not all services are available, perhaps because they weren't registered on the backend.

This also simplifies the process if there's a need to support InteractiveServer.
The DataLoaderService can then utilize dependency injection to access either the
HttpClient (for WASM) or EntityFramework (for Server) and fetch the data.

PS: you don't need to use generic types; this is since my component has a @typeparam T


    @if (_items == null)
    {
        // LoadingSpinner.gif
        <MudProgressCircular Color="Color.Default" Indeterminate="true" />
    }
    else
    {
        // display your data ...
    }
	
@code {	
    ...
    [Inject]
    public IServiceProvider ServiceProvider { get; set; } = null!;
	...
    private IValidator<T> _validatorService = null!;
    private ParameterizedDialogService _parameterizedDialogService = null!;
    private IDataLoaderService<T> _dataLoaderSevice = null!;
    private IDataUpdateService<T> _dataUpdateService = null!;
    ...
    private HashSet<T>? _items;
    ...
    private async Task LoadData() => _items = await _dataLoaderSevice.LoadData();

    protected override async Task OnInitializedAsync()
    {
        _dataLoaderSevice = ServiceProvider.GetService<IDataLoaderService<T>>()!;
        _dataUpdateService = ServiceProvider.GetService<IDataUpdateService<T>>()!;
        _parameterizedDialogService = ServiceProvider.GetService<ParameterizedDialogService>()!;
        _validatorService = ServiceProvider.GetService<IValidator<T>>()!;

        if (_dataLoaderSevice == null
            || _dataUpdateService == null
            || _parameterizedDialogService == null
            || _validatorService == null)
        {
			// exit since at least one service could not be retrieved
            return;
        }

        await LoadData();
    }

    // this LifeCylce hook is not called during preRender or SSR mode
    protected override void OnAfterRender(bool firstRender)
    {
        if (!firstRender)
        {
            return;
        }

        // ensure error messages if services are missing and we enter an Interactive Mode.
        // else the spinner will spinn forever without an error message.
        _dataLoaderSevice ??= ServiceProvider.GetRequiredService<IDataLoaderService<T>>();
        _dataUpdateService ??= ServiceProvider.GetRequiredService<IDataUpdateService<T>>();
        _parameterizedDialogService ??= ServiceProvider.GetRequiredService<ParameterizedDialogService>();
        _validatorService ??= ServiceProvider.GetRequiredService<IValidator<T>>();
    }
}
[MyTypeHttpDataLoaderService.cs]

public class MyTypeHttpDataLoaderService(HttpClient httpClient) : IDataLoaderService<MyType>
{
    private readonly HttpClient _httpClient = httpClient;

    public async Task<HashSet<MyType>> LoadData()
    {
        var items = await _httpClient.GetFromJsonAsync<HashSet<MyType>>("relativ url");
        return items ?? [];
    }
}
Register for WASM:
...
builder.Services.AddScoped<IDataLoaderService<MyType>, MyTypeHttpDataLoaderService>();
...

@nakamacchi
Copy link
Author

nakamacchi commented Oct 15, 2023

Thank you for suggestion, all. I understand the workaround and prerendering is working on the server-side only. So the discussion points are below. Note: This problem will be problematic only when in the mixed application (blazor server and blazor wasm in single application).

#1. Default prerendering mode should be always true in every application?
-> I understand that prerendering is very nice feature for SEO in such like B2C website, but it is not necessary needed for intranet application (LOB application) or in the authenticated page. In such page or application, prerendering is not good because for double work in some case.

#2. Component with InteractiveWebAssembly mode should be run in client-side?
-> I think the developer will expect this switch to control the location which the component should be run. But in default behavior (i.e., prerendering is enabled), The component with InteractiveWebAssembly mode will be run in the Server too. This is very confusing, and it seems that the programming model is broken.

So I want to recommend that the default prerendering mode should be disabled, or @rendermode without prerendering option should be prepared by built-in.

@mahald
Copy link

mahald commented Oct 15, 2023

The primary concern with disabling prerendering is that the page can appear unresponsive, especially if the dotnetWasm isn't cached and is being downloaded for the first time. With slow internet connectivity, this might result in users seeing a blank screen for several seconds after clicking a menu link. While this might be acceptable for an internal company dashboard, it's problematic for public-facing sites. Visitors might perceive the site as malfunctioning and either leave or continuously refresh the page. As a result, potential customers could be lost simply due to the absence of prerendering. Therefore, enabling it by default is advisable.

@javiercn
Copy link
Member

@nakamacchi thanks for contacting us.

As several folks point out, the default approach is to prerender. This is not something that we currently plan to change.

@ghost ghost locked as resolved and limited conversation to collaborators Nov 16, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-blazor Includes: Blazor, Razor Components
Projects
None yet
Development

No branches or pull requests

4 participants