Skip to content

Instantly share code, notes, and snippets.

@JerryNixon
Last active November 7, 2020 08:03
Show Gist options
  • Select an option

  • Save JerryNixon/8db0588b89da876c321d3557cd4cdf28 to your computer and use it in GitHub Desktop.

Select an option

Save JerryNixon/8db0588b89da876c321d3557cd4cdf28 to your computer and use it in GitHub Desktop.
Blazor troubles with rendering <option/>
@using Microsoft.AspNetCore.Components.Rendering
<EditForm Model="@Model">
<InputSelect class="form-control" @bind-Value="Model.Selection">
@{ MakeOption(__builder, "John"); }
@{ MakeOption(__builder, "Jack"); }
@{ MakeOption(__builder, "Mark"); }
</InputSelect>
</EditForm>
@code {
// https://stackoverflow.com/a/57840040/265706
// https://visualstudiomagazine.com/articles/2019/10/02/blazor-gotchas.aspx
public class Record
{
public string Selection { get; set; }
}
public Record Model { get; set; } = new Record();
void MakeOption(RenderTreeBuilder __builder, string name)
{
<option value="@name">@name</option>
}
}
@marin-bratanov

Copy link
Copy Markdown

I think this might have something to do with the form and that its contents are already a RenderFragment - it has a ChildContent parameter which is how you add content to it. Below is a sample that showcases the difference.

I personally never got the hang of this mixing HTML into your real C# code, and I never liked it so I am not 100% sure what's going on. Perhaps you could open an issue for MS to take a look at. The way I'd do this is to have a component render my child content and HTML rather than a function. If it is as simple as an option, I'd keep it in the markup. Maybe add a method to return a proper name or value if there are a couple of if-blocks in there.

caption repro of using the builder in a render fragment and out of it - in the render fragment it does not work, probably because the builder is already occupied with the rest of the fragment

@using Microsoft.AspNetCore.Components.Rendering

<EditForm Model="@Model">

    <InputSelect class="form-control" @bind-Value="Model.Selection">
        @{ MakeOption(__builder, "John"); }
        @{ MakeOption(__builder, "Jack"); }
        @{ MakeOption(__builder, "Mark"); }
    </InputSelect>

    @* will NOT render *@
    @{ MakePara(__builder, "John"); }

</EditForm>

@* will render *@
@{ MakePara(__builder, "Jack"); }

@code {

    // https://stackoverflow.com/a/57840040/265706
    // https://visualstudiomagazine.com/articles/2019/10/02/blazor-gotchas.aspx

    public class Record
    {
        public string Selection { get; set; }
    }

    public Record Model { get; set; } = new Record();

    void MakeOption(RenderTreeBuilder __builder, string name)
    {
        <option value="@name">@name</option>
    }

    void MakePara(RenderTreeBuilder __builder, string name)
    {
        <p id="@name">@name</p>
    }
}

@marin-bratanov

Copy link
Copy Markdown

After fiddling with this a bit more, I'm fairly certain the original approach does not work because you're in a RenderFragment that already has its own builder and simply does not plug yours into the rendering.

Here's how I hacked it to work (screenshot that tries to explain the ugliness is also below).

<EditForm Model="@Model" ChildContent="@( GetFormTemplate(new EditContext(Model)) )">
</EditForm>

@Model.Selection

@code {

    // https://stackoverflow.com/a/57840040/265706
    // https://visualstudiomagazine.com/articles/2019/10/02/blazor-gotchas.aspx

    public class Record
    {
        public string Selection { get; set; }
    }

    public Record Model { get; set; } = new Record();


    public RenderFragment<EditContext> GetFormTemplate(EditContext theEditContext)
    {
        RenderFragment<EditContext> MyTemplate = context => __builder =>
        {
        <InputSelect class="form-control" @bind-Value="@( (theEditContext.Model as Record).Selection )" 
                     ChildContent="@( GetOptions(new string[] { "John", "Jack", "Mark" }) )">
        </InputSelect>
        };

        return MyTemplate;
    }

    public RenderFragment GetOptions(string[] opts)
    {
        RenderFragment MyTemplate = __builder =>
        {
            <option value="">--SELECT--</option>
            foreach (var item in opts)
            {
                <option value="@item">@item</option>
            }
        };

        return MyTemplate;
    }
}

2020-11-07 09_53_46-Window

@marin-bratanov

Copy link
Copy Markdown

Third post- here's how I would do it - the method is to apply some logic that can now be pure and straightforward C#. If you don't need such complexity, do the text like the value - directly from the foreach.

<EditForm Model="@Model">

    <InputSelect class="form-control" @bind-Value="Model.Selection">
        @{
            string[] opts = new string[] { "John", "Jack", "Mark" };
            foreach (string item in opts)
            {
                <option value="@item">@GetComplexText(item)</option>
            }
        }
    </InputSelect>

    @Model.Selection

</EditForm>

@code {

    // https://stackoverflow.com/a/57840040/265706
    // https://visualstudiomagazine.com/articles/2019/10/02/blazor-gotchas.aspx

    public class Record
    {
        public string Selection { get; set; }
    }

    public Record Model { get; set; } = new Record();

    //that's how I'd get a more complex text or value for this if I needed it and it made the foreach unreadable
    string GetComplexText(string item)
    {
        return $"{item} {DateTime.Now.Ticks}";
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment