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.34k stars 9.98k forks source link

Event handlers in layout do not seem to fire in a .NET 8 RC1 blazor application #50712

Closed akorchev closed 1 year ago

akorchev commented 1 year ago

Is there an existing issue for this?

Describe the bug

Originally reported as a problem with the Radzen.Blazor component library which we maintain. After troubleshooting it turned out that no event handlers are firing (no exceptions or any other problems).

I tested without Radzen.Blazor and managed to reproduce the problem with a vanilla button click handler taken from the Counter.razor component that comes from the default application template.

For some reason event handlers defined in the layout do not fire out of the box.

Expected Behavior

I expected event handlers in layouts to work as in earlier Blazor and .NET versions. Maybe some configuration is missing (related to the new mixed rendering model).

Steps To Reproduce

  1. Create a new .NET 8 Blazor application by running dotnet new blazor -o BlazorNET8
  2. Open Components/MainLayout.razor.
  3. Delete everything and paste

    @inherits LayoutComponentBase
    
    <div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>
    
    <main>
        <div class="top-row px-4">
            <p role="status">Current count: @currentCount</p>
            <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
            <a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
        </div>
    
        <article class="content px-4">
            @Body
        </article>
    </main>
    </div>
    
    <div id="blazor-error-ui">
    An unhandled error has occurred.
    <a href="" class="reload">Reload</a>
    <a class="dismiss">🗙</a>
    </div>
    
    @code {
    private int currentCount = 0;
    
    private void IncrementCount()
    {
        currentCount++;
    }
    }
  4. Run the application and try clicking the Click me button in the header. The displayed counter value remains 0 instead of incrementing. In .NET 7 this works as expected.

Exceptions (if any)

No response

.NET Version

8.0.100-rc.1.23455.8

Anything else?

I am testing on macOS (M1, Ventura 13.4.1).

JasonWeise commented 1 year ago

Definintely does this on .Net 8 RC1 on Windows 10 as descibed above as well. I hope this bug doesn't make it into GA.

gragra33 commented 1 year ago

@akorchev By default, Blazor renders static pages. You need to tell Blazor how to render.

From your example, the Counter.razor page has the following attribute:

@attribute [RenderModeServer]

There are modes: aspcore/src/Components/Web/src/RenderMode/RenderMode.cs

There was a short-lived discussion here: Clarify the names of the interactive render modes for Blazor and rationalize with the component tag helper #50636

akorchev commented 1 year ago

Thank you for the clarification @gragra33. I tried adding @attribute [RenderModeServer] to MainLayout.razor and ended up with a different exception.

InvalidOperationException: Cannot pass the parameter 'Body' to component 'MainLayout' with rendermode 'ServerRenderMode'. This is because the parameter is of the delegate type 'Microsoft.AspNetCore.Components.RenderFragment', which is arbitrary code and cannot be serialized.

What are the required steps to enable events in the MainLayout? I tried with RenderModeServer(false), RenderModeWebAssembly. All lead to the same exception.

It sounds as related to https://github.com/dotnet/aspnetcore/issues/50433 but I am not sure.

I found this and it seems that in order to enable interactivity in the layout one has to create child components with their RenderMode set and use them.

This however makes it hard (if not impossible) to use @Body as the ChildContent of a component that needs interactivity.

gragra33 commented 1 year ago

@akorchev I was looking at a different issue #50724 and noticed that behavior too.

Yeah, the workaround is to have a wrapper component 'WrapperComponent .razor':

@ChildContent

@code {
    public RenderFragment ChildContent { get; set; }
}

Then in the MainLayout.razor:

<article class="content px-4">
    <WrapperComponent @rendermode="@RenderMode.Server">
        <ChildContent>
            @Body
        </ChildContent>
    </WrapperComponent>
</article>

I removed the render modes on the sample pages and this works.

akorchev commented 1 year ago

I see this is all by design. By the way we seem to have the same issue as #50724 ... Also I've spent 15 minutes wondering why a click event was not firing in Home.razor - it was all because @attribute [RenderModeServer] was missing. I wonder why static rendering was picked as the default mode.

gragra33 commented 1 year ago

@akorchev Yep, this will be a big headache as it breaks how it works by default.

I suggested an interactivity opt-out attribute, so interactive by default, but was told too much work at this late stage.

akorchev commented 1 year ago

I was having doubts about those mixed rendering modes but didn't expect components to not be interactive by default. We will see how the Blazor community reacts.

akorchev commented 1 year ago

I am closing the issue as it is the new default behavior. There will be a new switch in dotnet new blazor that will enable full application interactivity by default. Enabling it now is as simple as changing <Routes /> in App.razor to <Routes @rendermode=@RenderMode.Server />.

TimPurdum commented 1 year ago

This bit me today, same deal, I replace MainLayout with my own code on .NET 8 and had no interactivity. Worse, I share pages and components with MAUI hybrid, so sprinking "RenderMode" throughout that shared code seems like a dangerous idea. Is there no way to go back to .NET7 default server rendering, but still preserve the options of adding other modes in the future?

TimPurdum commented 1 year ago

Also, the workaround did not work, even with a WrapperComponent, I got the same error @akorchev described above.

iancoetzer commented 1 year ago

I am closing the issue as it is the new default behavior. There will be a new switch in dotnet new blazor that will enable full application interactivity by default. Enabling it now is as simple as changing <Routes /> in App.razor to <Routes @rendermode=@RenderMode.Server />.

Thank you, after almost two hours I found your solution.