【问题标题】:How to use RadioButtons and Checkboxes in an asp.net Blazor page如何在 asp.net Blazor 页面中使用 RadioButtons 和 Checkboxes
【发布时间】:2021-07-17 16:48:36
【问题描述】:

我发现在 asp.net Blazor(服务器端)页面中动态创建和使用单选按钮和复选框非常困难,主要是因为:

  • 我不知道如何在 <input type="radio"> 字段中绑定
  • 我很晚才发现EditForm
  • 我没有找到(m)任何示例
  • 必须使用循环局部变量来避免闭包(和索引越界错误)
  • <InputCheckbox> 似乎无法绑定到 List<bool> 并且需要复杂对象的列表并引发 ArgumentException: The provided expression contains a InstanceMethodCallExpression1 which is not supported. FieldIdentifier only supports simple member accessors (fields, properties) of an object. 错误。

所以我发布了这个问题并同时用一个可行的解决方案来回答它。下面的代码首先演示了常规<input> 字段的使用,然后使用EditForm 组件。据我所知,后者是首选解决方案。

我还有一个问题:如何使用List<bool> 代替复杂对象的列表?不得不使用单独的类来封装单个属性感觉不妥。如果使用 EF Core 持久化 formModel,它也会使事情复杂化。在类似的背景下讨论了相同的问题,例如here.

【问题讨论】:

    标签: asp.net blazor


    【解决方案1】:

    我认为你的回答让这件事复杂化了。

    下面的代码演示了一个基本设置(它是演示代码而不是生产代码)。

    它将EditForm 与模型一起使用。有链接到模型中的单选按钮和复选框可以正确更新。 Selected 有一个设置器,因此您可以在其中设置断点并查看谁在更新。

    @page "/"
    @using Blazor.Starter.Data
    
    <EditForm EditContext="this.editContext">
    
        @foreach (var model in models)
        {
            <h3>@model.Value</h3>
    
            <h5>Check boxes</h5>
            foreach (var option in model.Options)
            {
                <div>
                    <InputCheckbox @bind-Value="option.Selected" />@option.Value
                </div>
            }
            <h5>Option Select</h5>
            <div>
                <InputRadioGroup @bind-Value="model.Selected">
                    @foreach (var option in model.Options)
                        {
                        <div>
                            <InputRadio Value="option.Value" /> @option.Value
                        </div>
                        }
                </InputRadioGroup>
                <div>
                    Selected: @model.Selected
                </div>
            </div>
        }
    </EditForm>
    <button class="btn btn-dark" @onclick="OnClick">Check</button>
    
    @code {
    
        private EditContext editContext;
    
        private List<Model> models;
    
        protected override Task OnInitializedAsync()
        {
            models = Models;
            editContext = new EditContext(models);
            return Task.CompletedTask;
        }
    
        public void OnClick(MouseEventArgs e)
        {
            var x = true;
        }
    
        public List<Model> Models => new List<Model>()
        {
            new Model() { Value = "Fred"},
            new Model() { Value = "Jon"},
         };
    
    
        public class ModelOptions
        {
            public string Value { get; set; }
            public bool Selected
            {
                get => _Selected;
                set
                {
                    _Selected = value;
                }
            }
            public bool _Selected;
        }
    
        public class Model
        {
            public string Value { get; set; }
            public string Selected { get; set; }
            public List<ModelOptions> Options { get; set; } = new List<ModelOptions>()
            {
                new ModelOptions() {Value="Tea", Selected=true},
                new ModelOptions() {Value="Coffee", Selected=false},
                new ModelOptions() {Value="Water", Selected=false},
    
            };
        }
    }
    

    它是这样的:

    【讨论】:

    • 您的解决方案并没有太大的不同。您还可以将复选框绑定到位于复杂对象列表 (List&lt;ModelOptions&gt;) 中的 bool,而不是直接绑定到 List&lt;bool&gt;。所以你的模型类也包含第二个类ModelOptions,它不是那么优雅。
    • 当我第一次看到你的答案时,我迷失了细节和长度——我想很多其他人都会这样做——因此我对 over complicates 的评论。我刚刚经历过,是的,没什么不同。
    • 好吧,为了一个简单的答案,我不会手动创建EditContext,而是将模型类传递给EditForm
    • [礼貌] 继续前进,如果您不准备让其他人发表评论,请不要发帖。
    【解决方案2】:

    这是问题中提到的代码示例,其中包含更详细的 cmets。首先是没有EditForm 的版本,这不是推荐的方式,但很好地演示了闭包问题以及为什么需要循环局部变量。它还表明我未能使用 type="radiobutton"&lt;input&gt; 字段,因为似乎无法将值绑定到它:

    @page "/"
    
    <h1>Use Checkboxes and Radio Buttons in asp.net Blazor w/o EditForm</h1>
    
    <h2>Checkboxes using a for-loop and a List of bool</h2>
    Don't tick the left column, as it generates an index out of bounds error.
    @for (int i = 0; i < CheckboxList.Count(); i++)
    {
        // Gotcha: This is a closure, so the function call is stored together with the environment.
        // Since i resides outside of the loop, it is a single variable which is stored with the function call, so
        // index out of bound occurs because the last value is used. Instead, use a local variable, so for each
        // iteration, a new variable is used and bound to the function. This is a compiler gotcha.
        // Since C# 5.0, the loop variable of a foreach lop is inside the loop, so using foreach is save, but it
        // does not work with a List<bool> but requires a List of objects than contains a bool as in
        // foreach(bool items in CheckboxList), item is the iteration variable and cannot be assigned.
        // https://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/
        // https://stackoverflow.com/questions/58843339/getting-argumentoutofrangeexception-using-for-loop-in-blazor-component
        int ii = i;
    
        <div class="form-check">
            <input type="checkbox" @bind=@CheckboxList[i] /><!-- does not work, index out of range -->
            <input type="checkbox" @bind=@CheckboxList[ii] />
            <label>Answer @ii</label>
        </div>
    }
    Checkbox selection for-loop: @OutTextCheckboxFor
    <hr />
    
    
    <h2>Checkboxes using a foreach-loop and a List&lt;Item&gt;</h2>
    @foreach (CheckboxItem item in CheckboxItems)
    {
        <div class="form-check">
            <input type="checkbox" @bind=@item.IsChecked />
            <label>@item.Title</label>
        </div>
    }
    Checkbox selection foreach-loop: @OutTextCheckboxForEach
    <hr />
    
    
    
    <h2> Does not work: Radio Buttons using a foreach-loop and a List&lt;Item&gt;</h2>
    Somehow it seems as if binding a radio button does not work the same way as binding a checkbox. I would expect
    that item.Ischecked is true/false, depending on the selection of the radio button.
    @foreach (RadioItemBool item in RadioItems)
    {
        <div class="form-check">
            @* would need to add value=, but can't as it is already assigned by bind?? *@
            <input type="radio" @bind=@item.IsChecked />
            <label>@item.Title</label>
        </div>
    }
    Radio selection foreach-loop: @OutTextRadioForEach
    <hr />
    
    <button @onclick="OnSubmit">
        Evaluate all the above
    </button>
    <hr />
    
    
    @code {
    
        // could also use an array as a direct replacement for the list
        // could also be a field instead of a property
        public List<bool> CheckboxList { get; set; } = new List<bool> { true, false, true };
    
        public List<CheckboxItem> CheckboxItems = new List<CheckboxItem>() {
            new CheckboxItem(true, "Checkbox 1"), new CheckboxItem(false, "Checkbox 2"), new CheckboxItem(true, "Checkbox 3") };
    
        public List<RadioItemBool> RadioItems = new()
        {
            new RadioItemBool(false, "Radio 1"),
            new RadioItemBool(false, "Radio 2"),
            new RadioItemBool(false, "Radio 3")
        };
    
    
        string OutTextCheckboxFor;
        string OutTextCheckboxForEach;
        string OutTextRadioForEach;
    
    
        public class CheckboxItem
        {
            public bool IsChecked;
            public string Title;
            public CheckboxItem(bool isChecked, string title)
            {
                IsChecked = isChecked;
                Title = title;
            }
        }
    
        public class RadioItemBool
        {
            public bool IsChecked;
            public string Title;
            public RadioItemBool(bool isChecked, string title)
            {
                this.IsChecked = isChecked;
                this.Title = title;
            }
        }
    
    
        public void OnSubmit()
        {
            OutTextCheckboxFor = "";
            for (int i = 0; i < CheckboxList.Count(); i++)
            {
                OutTextCheckboxFor += " " + (CheckboxList[i] ? "1" : "0");
            }
    
            OutTextCheckboxForEach = "";
            foreach (CheckboxItem item in CheckboxItems)
            {
                OutTextCheckboxForEach += " " + (item.IsChecked ? "1" : "0");
            }
    
            OutTextRadioForEach = "";
            foreach (RadioItemBool item in RadioItems)
            {
                OutTextRadioForEach += " " + (item.IsChecked);
            }
        }
    }
    

    这是带有EditForm 的首选版本,还演示了文本输入的数据验证。它还表明,将复选框绑定到简单类型列表 (List&lt;bool&gt;) 似乎是不可能的,并且需要复杂类型列表 (List&lt;myBool&gt;) 来防止 ArgumentException: The provided expression contains a InstanceMethodCallExpression1 which is not supported. FieldIdentifier only supports simple member accessors (fields, properties) of an object. 异常 (c.f. here)。通常,复选框中显示的文本也可能存储在单独的类 (myBool) 中,但为了证明无法绑定到 bool 列表的缺点,只有 bool 位于单独的类中。

    @page "/"
    @using System.ComponentModel.DataAnnotations
    
    <h2>EditForm input with validation</h2>
    @* https://docs.microsoft.com/en-us/aspnet/core/blazor/forms-validation *@
    <EditForm Model="@formModel" OnValidSubmit="@HandleValidSubmit">
        <DataAnnotationsValidator />
        <ValidationSummary />
    
        <div class="form-group row">
            <label for="ShortText" class="col-sm-2 col-form-label">Short Text</label>
            <div class="col-sm-6">
                <InputText @bind-Value="formModel.ShortText" class="form-control" id="ShortText" aria-describedby="ShortTextHelp"
                           placeholder="Enter short text" />
                <small id="ShortTextHelp" class="form-text text-muted">This is a required field.</small>
            </div>
        </div>
    
        <div class="form-group row">
            <label for="Checkboxes" class="col-sm-2 col-form-label">Checkbox</label>
            <div id="Checkboxes" class="col-sm-10">
                @for (int i = 0; i < formModel.IsCheckedComplex.Count; i++)
                {
                    // prevent closures (IsCheckedComplex[ii] needs to be a loop-local variable)
                    int ii = i;
    
                    <div class="col-sm-10">
                        @* Unfortunately, using @bind-value with a List<bool> does not work (see comment below *1) *@
                        <InputCheckbox class="form-check-input" id="@ii" @bind-Value="@formModel.IsCheckedComplex[ii].IsChecked" />
                        <label class="form-check-label" for=@ii>"Text for item"</label>
                    </div>
                }
            </div>
        </div>
    
    
        <div class="form-group row">
            <label for="RadioButtons" class="col-sm-2 col-form-label">Radio buttons</label>
            <div id="RadioButtons" class="col-sm-10">
                <InputRadioGroup id="RadioButtons" @bind-Value=@formModel.SelectedRadio>
                    @foreach (var item in formModel.RadioItems)
                    {
                        <div class="col-sm-10">
                            <InputRadio class="form-check-input" id=@item.Index Value=@item.Index />
                            <label class="form-check-label" for=@item.Index>@item.Title</label>
                        </div>
                    }
                </InputRadioGroup>
            </div>
        </div>
    
    
        <div class="form-group row">
            <div class="col-sm-10">
                <button type="submit" class="btn btn-primary">Submit</button>
            </div>
        </div>
        <p>Inspect formModel in HandleValidSubmit() to see user inputs.</p>
    
    </EditForm>
    
    @code {
    
        private FormModel formModel = new();
    
        public class FormModel
        {
            public string Text { get; set; }
    
            [Required]
            [StringLength(10, ErrorMessage = "Short Text is too long.")]
            public string ShortText { get; set; }
    
    
            // === for the checkboxes
    
            // unfortunately, binding to a List<bool> is not possible; try replacing IsCheckedComplex with IsChecked above *1
            public List<bool> IsChecked { get; set; } = new() { true, false, false };
            public List<myBool> IsCheckedComplex { get; set; } = new() { new(true), new(false), new(false) };
    
    
            // a complex object is required as using List<bool> directly, throws an ArgumentException: The provided expression
            // contains a InstanceMethodCallExpression1 which is not supported. FieldIdentifier only supports simple member
            // accessors (fields, properties) of an object. See e.g. https://github.com/dotnet/aspnetcore/issues/12000
            public class myBool
            {
                public bool IsChecked { get; set; }
    
                // store the text shown in the checkbox items here as well
    
                public myBool(bool init)
                {
                    IsChecked = init;
                }
    
            }
    
    
            // === for the radio buttons
    
            public int SelectedRadio = 2;
    
            public class RadioItem
            {
                public int Index;
                public string Title;
                public RadioItem(int index, string title)
                {
                    this.Index = index;
                    this.Title = title;
                }
            }
    
            public List<RadioItem> RadioItems = new()
            {
                    new RadioItem(1, "Radio 1"),
                    new RadioItem(2, "Radio 2"),
                    new RadioItem(3, "Radio 3")
            };
        }
    
        
        private void HandleValidSubmit()
        {
            // HandleValidSubmit called
        }
    }
    

    【讨论】:

    • 如果不想长篇大论,可以把@for (int i = 0; i &lt; CheckboxList.Count(); i++)换成@foreach (int i in Enumerable.Range(0, CheckboxList.Count())),省略int ii = i
    • 是的,因为 C# 5.0 foreach 使用循环局部变量,并且不需要 int ii=i,正如 here 所解释的那样。
    猜你喜欢
    • 2021-08-21
    • 2011-08-29
    • 1970-01-01
    • 2020-03-14
    • 2021-12-13
    • 2021-11-11
    • 2021-08-16
    • 2021-06-03
    • 2021-03-31
    相关资源
    最近更新 更多