Search code examples
razorblazor

How do we create a reusable RenderFragment in a separate code file?


In this Microsoft article, they show how to extract a piece of Blazor code into a method so that it can be reused. They show the following example...

<h1>Hello, world!</h1>

@RenderWelcomeInfo

<p>Render the welcome info a second time:</p>

@RenderWelcomeInfo

@code {
    private RenderFragment RenderWelcomeInfo = __builder =>
    {
        <p>Welcome to your new app!</p>
    };
}

They then go on to explain that if you want to extract this method into a separate .razor file, you can make it static.

That's all fine for a simple example such as the ones they show, but if you want to anything realistic, chances are you will need to pass at least one parameter to the method.

However, if you change their SayHello example to take a parameter...

  public static RenderFragment SayHelloName(string name) = __builder => {
    <h1>Hello @name</h1>
  };

...then you get an interesting mix of compiler errors...

  • The = sign has a red underline, and hovering over it gives an error Function body expected

  • The variable __builder is highlighted in red, and hovering over it gives an error Cannot resolve symbol __builder

  • The space right after the variable __builder is highlighted in red, and hovering over it gives an error Cannot resolve symbol __builder\n\nUnexpected token

  • The => has a red underline, and hovering over that gives an error Not all code paths return a value in lambda expression of type 'RenderFragment' (which is interesting, as the return type doesn't have a generic type)

  • The usage of name in the body of the method is red with an error Cannot resolve symbol 'name'

  • The }; right at the end is underlined in red and shows an error of } expected, with the opening braces being the class declaration.

To confuse matters more, only the 4th and 5th of these errors show up in the Error panel, albeit with slightly different wording.

Following the example show in Telerik's KB, I tried the following...

  public static RenderFragment<object> SayHelloName(string name) => context => __builder   => {
    <h1>Hello @name</h1>
  };

However, when I try to use this...

<div>@SayHelloName("Jim")</div>

...the following is rendered on the page...

Microsoft.AspNetCore.Components.RenderFragment`1[System.Object]

...instead of "Hello Jim" as I expected.

Anyone able to explain what I'm doing wrong? How do I extract a RenderFragment method that takes parameters into a separate class?


Solution

  • Reading a bit further down the page you linked to:

    RenderFragment delegates can also accept parameters. The following component passes the message (message) to the RenderFragment delegate:

    <div class="chat">
        @foreach (var message in messages)
        {
            @ChatMessageDisplay(message)
        }
    </div>
    
    @code {
        private RenderFragment<ChatMessage> ChatMessageDisplay = message => __builder =>
        {
            <div class="chat-message">
                <span class="author">@message.Author</span>
                <span class="text">@message.Text</span>
            </div>
        };
    }
    

    So it looks like you need

    public static RenderFragment<string> SayHelloName = name => __builder => {
        <h1>Hello @name</h1>
    };
    

    If you want to pass in more parameters, wrap them in a type e.g Greeting:

    public class Greeting
    {
        public string Title { get; set; }
        public string Name { get; set; }
    }
    

    and then pass the Greeting as a type parameter similar to ChatMessage example in the docs:

    public static RenderFragment<Greeting> SayHelloName = greeting=> __builder => {
            <h1>Hello @greeting.Title @greeting.Name</h1>
        };
    

    Or you can use a non-generic version:

    public static RenderFragment SayHelloName(string title, string name) => __builder => {
        <h1>Hello @greeting.Title @greeting.Name</h1>
    };