dotnet/blazor-samples

BlazorWebAppOidc - I added an InteractiveServer component - component renders on prerender but becomes 404 when interactiveserver takes over

Closed this issue · 8 comments

I copied and pasted the component Client/Pages/Weather.razor to Server Project/Components/Pages.
I updated the WeatherServerSide.razor to use @rendermode InteractiveServer. The prerender loads the component and then it "switches over" to interactive server? At that point the app returns a 404. I know this is probably simple, but are server-side components supported in this particular project? Additionally, if they are supported, is interactiveserver auth supported too?

Here is my code for the updated WeatherServerSide.razor

@page "/weatherserverside"
@using Microsoft.AspNetCore.Authorization
@using BlazorWebAppOidc.Client.Weather
@implements IDisposable
@inject PersistentComponentState ApplicationState
@inject IWeatherForecaster WeatherForecaster
@rendermode InteractiveServer

<PageTitle>Weather Server</PageTitle>

<h1>Weather Server</h1>

<p>This component demonstrates showing data.</p>

@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>
}

@code {
    private IEnumerable<WeatherForecast>? forecasts;
    private PersistingComponentStateSubscription persistingSubscription;
    private List<string> userIds;
    private int top = 100;
    private int itemsCount = 1;

    protected override async Task OnInitializedAsync()
    {
        persistingSubscription = ApplicationState.RegisterOnPersisting(PersistData);

        if (!ApplicationState.TryTakeFromJson<IEnumerable<WeatherForecast>>(nameof(forecasts), out var restoredData))
        {
            forecasts = await WeatherForecaster.GetWeatherForecastAsync();
        }
        else
        {
            forecasts = restoredData!;
        }

    }

    private Task PersistData()
    {
        ApplicationState.PersistAsJson(nameof(forecasts), forecasts);

        return Task.CompletedTask;
    }

    void IDisposable.Dispose() => persistingSubscription.Dispose();
}

@Takhoffman ... I'll take a look at this on Thursday morning and get back to you.

UPDATE ... Actually, it will be sometime today ... afternoon or evening perhaps. I'm 🏃😅 at the moment.

@guardrex I appreciate the updates! I eagerly await your input. Please take care of whatever you need to take care of 🫡.

@Takhoffman ... Move your component to the client's Pages folder. I think it will work there, AND let's leave this issue open for a bit because I need to go look and see what we're saying about where interactive server components go in an Auto (with .Client) project. I'll need a few days to reach it, and I'll probably open an new issue for it over on the docs repo. For now, just check and see if it works ok from the client project.

Ok ... I opened an issue to improve the guidance on that point. I think it will end up either a new section or an IMPORTANT note in the Render Modes article. We can close this here. The work will be tracked by the docs repo for doc work. Thanks for calling this out, @Takhoffman.

@guardrex The component works in the client project. I was hopeful that I could jointly use InteractiveServer and WebAssembly rendered components in that project. I also planned to turn off pre-rendering in my particular project because it's not needed. My reason for these questions is that I have an existing server-side project but I wanted to slowly transition the existing app over to WASM so it would have been nice if this project template allowed me to do that. It's not the end of the world.

My understanding is that when you render a component interactive SSR (your component example ☝️) placed in the .Client project, it will truly be interactively rendered on the server. It's just unexpected that it would be placed in the .Client project, since "client" implies "on the client" or "rendered by the client." So .....

jointly use InteractiveServer and WebAssembly rendered components in that project

You can ... but they'll all be in the .Client project.

When I reach the docs issue for work, I'll see about making this point clearer, and I'll ask Mackinnon to review the PR.

I'm going to try and get this resolved soon. I'll try to work on it on Monday morning.

I brought the component client side like you suggested and got this error.
blazor.web.js:1 crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: Cannot create a component of type 'BlazorWebAppOidc.Client.Weather.TestServerSide' because its render mode 'Microsoft.AspNetCore.Components.Web.InteractiveServerRenderMode' is not supported by WebAssembly rendering.
System.NotSupportedException: Cannot create a component of type 'BlazorWebAppOidc.Client.Weather.TestServerSide' because its render mode 'Microsoft.AspNetCore.Components.Web.InteractiveServerRenderMode' is not supported by WebAssembly rendering.
at Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer.ResolveComponentForRenderMode(Type componentType, Nullable1 parentComponentId, IComponentActivator componentActivator, IComponentRenderMode renderMode) at Microsoft.AspNetCore.Components.ComponentFactory.InstantiateComponent(IServiceProvider serviceProvider, Type componentType, IComponentRenderMode callerSpecifiedRenderMode, Nullable1 parentComponentId)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.InstantiateChildComponentOnFrame(RenderTreeFrame[] frames, Int32 frameIndex, Int32 parentComponentId)
at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewComponentFrame(DiffContext& diffContext, Int32 frameIndex)
at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewSubtree(DiffContext& diffContext, Int32 frameIndex)
at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InsertNewFrame(DiffContext& diffContext, Int32 newFrameIndex)
at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.AppendDiffEntriesForRange(DiffContext& diffContext, Int32 oldStartIndex, Int32 oldEndIndexExcl, Int32 newStartIndex, Int32 newEndIndexExcl)
at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.ComputeDiff(Renderer renderer, RenderBatchBuilder batchBuilder, Int32 componentId, ArrayRange1 oldTree, ArrayRange1 newTree)
at Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment, Exception& renderFragmentException)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderInExistingBatch(RenderQueueEntry renderQueueEntry)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()

I will be on standby while you find out more information! Thank you.

Did you change something else in the app (e.g., the global render mode from Auto)?

It works here ...

image