How can I dynamically create a RenderFragment within a Method Parameter?

115 Views Asked by At

The following Code generates the output <p>sample</p> and shows how the compiler allows the creation of RenderFragments using regular razor code.

@stringFragmentWithParam("sample")

@code{
    private RenderFragment<string> stringFragmentWithParam = s => (__builder) =>
    {
        <p>@s</p>
    };
}

Now I want to use this concept create Extension Methods in the following manner (this example could yield <div><p>sample</p></div>):

// somewhere in a static class:
public static RenderFragment RenderInDiv(this string value, RenderFragment<string> fragment)
{
    // Omitted
}

@code {
    string somestring = "sample"
}

@somestring.RenderInDiv(s => (__builder) =>
    {
        <p>@s</p>
    })

But this doesn't compile. The compiler doesn't like the <p>@s</p> part, even though it's the exact same thing like in the previous example.

Please note, that I know how to implement the RenderInDiv() Method, I'ts just a simplified example of my real usecase. I also know how to implement a component that does the same thing. My Question is how could I call methods like that directly building the Renderfragment inline? Because using the RenderFragment from the first example with the RenderInDiv() Method will work. @somestring.RenderInDiv(stringFragmentWithParam)

2

There are 2 best solutions below

2
spzvtbg On

it won't compile because it is already in C# context and Razor syntax is not allowed there.

You can instead keep the RenderFragment field in the Razor code and use it as a parameter.

@somestring.RenderInDiv(stringFragmentWithParam)

@code {
    string somestring = "sample";

    private RenderFragment<string> stringFragmentWithParam = s => (__builder) =>
    {
        <p>@s</p>
    };
}

... or you can create templates using only extension methods

@somestring.RenderInP().RenderInDiv()

...

public static class RenderFragmentExtensions
{
    public static RenderFragment RenderInP(this string parameter)
    {
        return (__builder) =>
        {
            __builder.OpenElement(0, "p");
            __builder.AddContent(1, parameter);
            __builder.CloseComponent();
        };
    }

    public static RenderFragment RenderInDiv(this RenderFragment innerFragment)
    {
        return (__builder) =>
        {
            __builder.OpenElement(0, "div");
            __builder.AddContent(1, innerFragment);
            __builder.CloseComponent();
        };
    }

    public static RenderFragment RenderInDiv(this string parameter, RenderFragment<string> innerContent)
    {
        return (__builder) =>
        {
            __builder.OpenElement(0, "div");
            __builder.AddContent(1, innerContent(parameter));
            __builder.CloseComponent();
        };
    }
}

... or static methods in razor page and calling them from anywhere.

@using static (the razor_page class - optional, depends on you)
@RenderInDiv(RenderInP(this.somestring))

...

@code {
    public static RenderFragment<string> RenderInP = parameter => (__builder) => 
    {
        <p>@parameter</p>
    };

    public static RenderFragment<RenderFragment> RenderInDiv = innerFragment => (__builder) => 
    {
        <div>@innerFragment</div>
    };
}
3
Brian Parker On

For maintainability create a component with the desired output:

SomeComponent

<p>@Name</p>
@code {
    [Parameter]
    public string Name { get; set; } = default!;
}

Extension Method:

public static RenderFragment ToRenderFragment(this string name)
{
    return builder =>
    {
        builder.OpenComponent<SomeComponent>(sequence: 1);
        builder.AddComponentParameter(sequence: 2, name: "Name", name);
        builder.CloseComponent();
    };
}

If you have multiple components with the same parameters:

public static RenderFragment ToRenderFragment<TComponent>(this string name)
        where TComponent : notnull, IComponent, ISomeCommonInterface
{
    return builder =>
    {
        builder.OpenComponent<TComponent>(sequence: 1);
        builder.AddComponentParameter(sequence: 2, name: "Name", name);
        // Add all interface members.
        builder.CloseComponent();
    };
}

Example usage in a C# file.

internal class SomeClass
{
    public string Name { get; set; }
    public RenderFragment NameRenderFragment => Name.ToRenderFragment();
}