Search code examples
.netformsasp.net-coreblazorblazor-server-side

Blazor 8 SSR: Model Binding Complex objects with List


I can not get a Blazor EditForm with complex objects to work.

Here's an example of a simple Dice Roller, where the user can select the amount of dices and dice types he wants to roll. When Submitting the form, the formData variable is always null.

What am I doing wrong?

@using DiceMaster.Lib.Dices
@using System.ComponentModel
<EditForm Enhance Model="@formData" OnValidSubmit="@RollDices" FormName="DiceRoller">
   
    @foreach (var am in formData.Amounts)
    {
        <InputSelect @bind-Value="am.DiceTypeId">
            @foreach (DiceType ty in diceTypeGetter.GetDiceTypes())
            {
                <option value="@ty.TypeId">@ty.Name</option>
            }
        </InputSelect>
        <InputNumber @bind-Value="am.DiceCount" min="1" max="1000000"></InputNumber>
    }


    <button type="submit">Roll</button>
</EditForm>

@if (rollResults.Count > 0)
{
    foreach (DiceRollResult result in rollResults.OrderBy(r=>r.DiceTypeId).ThenBy(r=>r.SortIndex))
    {
        <div>@result.Value</div>
    }
}

@code {
    [SupplyParameterFromForm]
    public RoleDiceFormInfo formData { get; set; }

    private DiceTypeGetter diceTypeGetter { get; set; } = new DiceTypeGetter();
    private List<DiceRollResult> rollResults { get; set; } = new List<DiceRollResult>();
    protected override void OnInitialized()
    {
        if (formData is null)
        {
            formData = new() {Amounts = new List<DiceTypeAmmount>()
            {
                new DiceTypeAmmount()
                {
                    DiceCount = 1, DiceTypeId = diceTypeGetter.GetDiceTypes().First(d=>d.Name == "D6").TypeId
                },new DiceTypeAmmount()
                {
                    DiceCount = 1,DiceTypeId = diceTypeGetter.GetDiceTypes().First(d=>d.Name == "D3").TypeId
                }
            }};
        }
       
    }

    private void RollDices()
    {
        DiceRoller roller = new DiceRoller();
        DiceRollRequest request = new DiceRollRequest();
        request.DiceAmmounts = new List<DiceTypeAmmount>();
        request.DiceAmmounts.AddRange(formData.Amounts);
        List<DiceRollResult> results = roller.RollDices(request);
        rollResults = results;
    }

    public class RoleDiceFormInfo
    {
        public List<DiceTypeAmmount> Amounts { get; set; }
    }
    
    public class DiceTypeAmmount()
    {
        public Guid DiceTypeId { get; set; }
        public int DiceCount { get; set; }
    }
}

I expect the formData model to bind correctly


Solution

  • the name of the select would be rendered as am.DiceTypeId:

    enter image description here

    However,in static ssr ,model binding would perform as MVC/RazorPage,you could check this document for more details

    requires the name to be rendered as

    formData.Amounts[index].DiceCount
    

    I tried to modify the name attribute with InputSelect/InputNumber component ,but both failed whatever in for loop /foreach loop

    If you would accept dropping InputSelect/InputNumber component,the codes below would work for you:

    @{
        int index = 0;
    }
    @foreach (var am in formData.Amounts)
    {
    
    
        <select name="formData.Amounts[@index].DiceTypeId" @bind="@formData.Amounts[@index].DiceTypeId">
            //I don't know the codes in your GetDiceTypes() method
            <option value="@Guid.NewGuid()">Type1</option>
            <option value="@Guid.NewGuid()">Type2</option>
        </select>
        <input type="number" name="formData.Amounts[@index].DiceCount" @bind="@formData.Amounts[@index].DiceCount" min="1" max="1000000">
    
         index++;
     
    }
    

    It would work on myside:

    enter image description here