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

Blazor local functions in ChildContent don't work correctly #10236

Open
EngieDev opened this issue Apr 7, 2024 · 3 comments
Open

Blazor local functions in ChildContent don't work correctly #10236

EngieDev opened this issue Apr 7, 2024 · 3 comments
Labels
area-blazor area-compiler Umbrella for all compiler issues bug Something isn't working investigate

Comments

@EngieDev
Copy link

EngieDev commented Apr 7, 2024

When I call local functions in @{} block inside a .razor component tag the content from the local function isn't getting rendered.

Home.razor

@page "/"

<h1>Issue showcase</h1>

<IssueComponent>
   <span>Hello 1</span>
</IssueComponent>

<IssueComponent>
   @{
       IssueLocalFunction();
   }
</IssueComponent>

@{
   void IssueLocalFunction()
   {
       <span>Hello 2</span>
   }
}

IssueComponent.razor

@ChildContent

@code {
    [Parameter]
    public RenderFragment? ChildContent { get; set; }
}

Output

image

It's getting showed when is prerendering but then it disappears.
Here is my repo with that issue:
https://github.com/EngieDev/BlazorComponentsIssue

@davidwengier davidwengier added the area-compiler Umbrella for all compiler issues label Apr 7, 2024
@chsienki
Copy link
Contributor

@mkArtakMSFT @javiercn @SteveSandersonMS This seems to be a runtime issue. The output occurs during server side rendering, then disappears when the page updates its render mode.

The code being generated by the compiler is:

    public partial class Home : global::Microsoft.AspNetCore.Components.ComponentBase
    #nullable disable
    {
        #pragma warning disable 1998
        protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
        {
            __builder.AddMarkupContent(0, "<h1>Issue showcase</h1>\r\n\r\n");
            __builder.OpenComponent<global::BlazorComponentsIssue.Client.Views.Components.IssueComponent>(1);
            __builder.AddAttribute(2, "ChildContent", (global::Microsoft.AspNetCore.Components.RenderFragment)((__builder2) => {
                __builder2.AddMarkupContent(3, "<span>Hello 1</span>");
            }
            ));
            __builder.CloseComponent();
            __builder.AddMarkupContent(4, "\r\n\r\n");
            __builder.OpenComponent<global::BlazorComponentsIssue.Client.Views.Components.IssueComponent>(5);
            __builder.AddAttribute(6, "ChildContent", (global::Microsoft.AspNetCore.Components.RenderFragment)((__builder2) => {
#nullable restore
#line (10,7)-(12,5) "C:\Projects\scratch\BlazorComponentsIssue\BlazorComponentsIssue\BlazorComponentsIssue.Client\Views\Pages\Home.razor"

        IssueLocalFunction();
    

#line default
#line hidden
#nullable disable

            }
            ));
            __builder.CloseComponent();
#nullable restore
#line (15,3)-(18,1) "C:\Projects\scratch\BlazorComponentsIssue\BlazorComponentsIssue\BlazorComponentsIssue.Client\Views\Pages\Home.razor"

    void IssueLocalFunction()
    {

#line default
#line hidden
#nullable disable

            __builder.AddMarkupContent(7, "<span>Hello 2</span>");
#nullable restore
#line (19,1)-(20,1) "C:\Projects\scratch\BlazorComponentsIssue\BlazorComponentsIssue\BlazorComponentsIssue.Client\Views\Pages\Home.razor"
    }

#line default
#line hidden
#nullable disable

Which seems plausibly correct, but I guess the local function call is throwing something off. Does the builder get replaced, meaning the one captured in the closure of the local function would now be wrong?

@chsienki chsienki added this to the 17.11 Planning milestone Apr 11, 2024
@chsienki chsienki added bug Something isn't working investigate area-blazor and removed untriaged labels Apr 11, 2024
@jjonescz
Copy link
Contributor

jjonescz commented Apr 12, 2024

@chsienki I think the problem is that the local function uses __builder but is wrapped inside lambda __builder.AddAttribute("ChildContent", (__builder2) => { ... });, i.e., it should be using __builder2 to work properly.

@SteveSandersonMS
Copy link
Member

SteveSandersonMS commented Apr 15, 2024

I think the problem is that the local function uses __builder but is wrapped inside lambda __builder.AddAttribute("ChildContent", (__builder2) => { ... });, i.e., it should be using __builder2 to work properly.

Yes, that's exactly the problem.

To be honest this syntax should not exist:

   void IssueLocalFunction()
   {
       <span>Hello 2</span>
   }

Any syntax like that is very dangerous as it's capturing the __builder and writing to it at unknown times. The behavior is undefined and could easily result in corrupted output or random crashes.

We never added any syntax like that on purpose, but it happened on its own when C# added local functions in general. As far as I understand, Razor inherited the feature by default even though it's actively harmful in this case.

If it's somehow possible to prevent local functions in .razor, or for a more precisely-targeted fix: stop __builder getting captured in a closure via some analyzer/diagnostic, that would be ideal. Otherwise we have to just hope developers don't do this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor area-compiler Umbrella for all compiler issues bug Something isn't working investigate
Projects
None yet
Development

No branches or pull requests

5 participants