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.44k stars 10.02k forks source link

Blazor WebApp Wasm HttpClient Error Even on Prerendered False #53355

Closed mckaragoz closed 8 months ago

mckaragoz commented 10 months ago

Is there an existing issue for this?

Describe the bug

I see InteractiveWebAssembly has some issues on prerendering with service register described in here https://learn.microsoft.com/en-us/aspnet/core/blazor/call-web-api?view=aspnetcore-8.0&pivots=webassembly#client-side-services-for-httpclient-fail-during-prerendering

But this solution also doesn't work for me.

Expected Behavior

Should not give the error.

Steps To Reproduce

Exceptions (if any)

InvalidOperationException: Cannot provide a value for property 'Http' on type 'TestWebAppWasm.Client.Pages.Counter'. There is no registered service of type 'System.Net.Http.HttpClient'.

.NET Version

.NET 8

Anything else?

This doesn't occur when you navigate between interactive wasm pages. But navigating between account and wasm pages, this error occurs.

When i changed main project's App.razor from

private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account")
    ? null
    : InteractiveWebAssembly;

to

private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account")
    ? null
    : new InteractiveWebAssemblyRenderMode(prerender: false);

Doesn't give error, but this time it takes time and users wait for thw white empty screen.

My problem is neither adding builder.Services.AddHttpClient(); in main project or setting prerendering false on the required page are not working.

ghost commented 9 months ago

Thank you for filing this issue. In order for us to investigate this issue, please provide a minimal repro project that illustrates the problem without unnecessary code. Please share with us in a public GitHub repo because we cannot open ZIP attachments, and don't include any confidential content.

mckaragoz commented 9 months ago

This is the repo: https://github.com/mckaragoz/TestWebAppWasm

As you see only added the HttpClient as service in standard template.

Just try to navigate from login (or register) page to counter page.

mckaragoz commented 9 months ago

A little more info: Addingbuilder.Services.AddHttpClient(); into main project's Program.cs works for InteractiveAuto, but not for InteractiveWebassembly

MackinnonBuck commented 9 months ago

Thanks for reaching out. If you want a service to be available on the server (either when prerendering or during server interactivity) and in WebAssembly, you need to register the service in both the server and client projects. If you do this, does the issue resolve? If not, could you please provide a minimal repro that demonstrates the bug and has an HttpClient registered in both the server and client projects?

ghost commented 9 months ago

Hi @mckaragoz. 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.

mckaragoz commented 9 months ago

Thanks for reaching out. If you want a service to be available on the server (either when prerendering or during server interactivity) and in WebAssembly, you need to register the service in both the server and client projects. If you do this, does the issue resolve? If not, could you please provide a minimal repro that demonstrates the bug and has an HttpClient registered in both the server and client projects?

Hi this is the repro https://github.com/mckaragoz/TestWebAppWasm . Registering services on both projects works on InteractiveAuto but not InteractiveWebAssembly

MackinnonBuck commented 9 months ago

@mckaragoz, it looks like the Program.cs in the server project doesn't register an HttpClient. Could you update your repro to do so, and if the problem still exists, let us know? Then we will consider investigating this further. Thanks!

lnaie commented 8 months ago

@mckaragoz, thanks for taking the time to report one of the quirks around the rendering design.

In a SSR project I have a page set to render in InteractiveWebAssembly (it will render SS (server side) and CS (client side/wasm) as well -- because by default pretender = true).

In my client project BlazorSSR.Web, I want to show a list loaded from an API, but with pre-rendering. I'm testing, so it looks like this:

@page "/weather"
@* @rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false)) *@
@rendermode RenderMode.InteractiveWebAssembly

<PageTitle>API Weather forecast</PageTitle>

<h1>API Weather forecast</h1>

<p>This component demonstrates fetching data from the server: on Page Load it's SS pre-rendered, on Button click it's API reloaded.</p>

<button class ="btn btn-primary" @onclick=@Load>Reload</button>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@using BlazorSSR.Web.Shared
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IApiClient _apiClient
@code {
    WeatherForecast[]? forecasts;
    protected override async Task OnInitializedAsync() {
        await Load();
    }
    async Task Load() {
        forecasts = await _apiClient.GetWeatherForecasts();
    }
}

I have two separate implementations of the IApiClient, registered with the DI services:

With the current setup, this will execute once on SS, then one more time on CS in wasm. Obviously that is a problem, nobody wants to waste resources. Now, if I switch the rendering to @rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false)) then I get the page rendered only on CS. Good, but I still like the idea of pre-rendering on SS.

To recap, I would like the list to be pre-rendered SS and that's it -- the CS should no happen because the list is already rendered. Then if I press the Reload button on CS the same list is loaded from the API -- that works by the way. A simple use-case, using the current Blazor lifecycle events. Could anyone point out how do I achieve this with the current Blazor design, without using Streaming Rendering?

Stuart88 commented 7 months ago

Hitting this same problem now. Haven't tried Blazor since about .NET6.

Blazor was easy to get started and spin up a site rapidly, that's why it was great. Now on .NET8 I've wasted two hours with zero progress just trying to make httpclient work clientside. These new render modes have turned Blazor into a mess.