Search code examples
c#razorblazor

What is the easiest way to return multiple components in one RenderFragment?


A RenderFragment expects only one component to be returned, but that one component may have nested components:

private RenderFragment CreateRenderFragment()
{
    // This works
    return
    @<div>
        <text>Hello</text>
        <text>world!</text>
    </div>;

    // and so does this
    // return @<span><text>Hello</text> <text>world!</text></span>;
}

If we don't want to enclose the components in a <div> or <span> we can create a razor component following the ChildContent convention, e.g. ChildContentComponent:

@ChildContent

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

and then use this component as a wrapper:

private RenderFragment CreateRenderFragment()
{
    return
    @<ChildContentComponent>
        <text>Hello</text>
        <text>world!</text>
    </ChildContentComponent>;
}

I may be mising something, but I can't see any other way to create a RenderFragment with multiple components. Neither of the following compile:

private RenderFragment CreateRenderFragment1()
{
    return @<text>Hello</text> <text>world!</text>;
}

private RenderFragment CreateRenderFragment2()
{
    return
    @:@{
        <text>Hello</text>
        <text>world!</text>
    } // ; expected, but will not compile with, either
}

Is there an easier way than the ChildContentComponent approach, or is there already a standard component which simply declares a public RenderFragment ChildContent { get; set; }?

EDIT: Based on MrC aka Shaun Curtis's answer below, the solution is __builder => { ... };

private RenderFragment CreateRenderFragment3()
{
    return __builder =>
    {
        <Widget1>Hello</Widget1>
        <Widget2>world!</Widget2>
    };
}

Edit 2: And as Dimitris Maragkos and H H pont out below, <text> is a special do-nothing wrapper which is equivalent:

private RenderFragment CreateRenderFragment3()
{
    <text>
        <Widget1>Hello</Widget1>
        <Widget2>world!</Widget2>
    </text>
}

Solution

  • A RenderFragment is a delegate defined like this:

    public delegate void RenderFragment(RenderTreeBuilder builder);
    
    public delegate RenderFragment RenderFragment<TValue>(TValue value);
    

    This is my ChildContentComponent so it stands out on the page:

    <div class="bg-light m-2 p-2 border">
        @ChildContent
    </div>
    
    @code
    {
        [Parameter] public RenderFragment? ChildContent { get; set; }
    }
    

    I think this block of code may help you. Apologies if it confuses. Any questions, ask. Components, the Renderer and RenderFragments take a while to understand.

    @page "/"
    
    <PageTitle>Index</PageTitle>
    
    <h1>Hello, world!</h1>
    
    Welcome to your new app.
    
    @MyForm
    
    @code {
        // These two blocks use mixed C# and Razor markup
        // it works because this is a Razor file so will be compiled by the Razor Compiler
        // __builder is the RenderTreeBuilder provided by BuildRenderTree in a Razor component file
        private RenderFragment MyForm => (__builder) =>
        {
            <ChildContentComponent ChildContent=this.ABitOfContent />
    
            <ChildContentComponent>Another Hello</ChildContentComponent>
    
            <ChildContentComponent>And Another!!</ChildContentComponent>
    
            <ChildContentComponent>
                @this.ABitOfContent
                @this.ABitOfContent
                @this.ABitOfContent
                @this.AnotherBitOfContent
                <ChildContentComponent>
                    @this.AnotherBitOfContent
                </ChildContentComponent>
            </ChildContentComponent>
    
            @this.AnotherBitOfContent
        };
    
        private RenderFragment ABitOfContent = (__builder) =>
        {
            <div>Hello Blazor</div>
        };
    
    
        // This block is an example of what the Razor Compiler compiles a markup block into in the C# file
        private RenderFragment AnotherBitOfContent => (builder) =>
        {
            builder.OpenComponent<ChildContentComponent>(0);
            builder.AddAttribute(1, "ChildContent", this.ABitOfContent);
            builder.CloseComponent();
        };
    }
    

    You can view the C# classes that the Razor compiler emits from Razor files by adding EmitCompilerGeneratedFiles to the project file:

    <Project Sdk="Microsoft.NET.Sdk.Web">
    
      <PropertyGroup>
          <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
      </PropertyGroup>
    
    </Project>
    

    Files are emitted to:

    ./obj/Debug/netX.0/generated