使用RadioButtonFor正确编码一组单选按钮的方法

11

根据我的研究,MVC中编写复选框的正确方法如下。

@Html.CheckBoxFor(m => m.RememberMe)
@Html.LabelFor(m => m.RememberMe)

这将呈现一个复选框,为复选框呈现标签,并使标签可点击,这意味着单击标签也会切换复选框。

但是,如果一个视图模型字段有多个复选框怎么办?(例如,也许每个复选框代表整数值中的一位。)或者更好的选择是,如果有几个选项与同一字段相关联,那么如何处理单选按钮?

在这些情况下,上面的代码似乎存在问题。要么不是所有元素都与同一字段相关联,要么渲染了具有相同ID的多个元素。

我已经研究了一段时间了。我找到了很多解决方案;然而,它们似乎要么没有可点击的标签,要么需要大量的自定义和/或非标准编码。一些人完全避免使用RadioButtonFor / LabelFor,而是选择编写原始HTML。

编写原始HTML是可以的,但 MVC 的设计人员难道没有预料到我们可能需要同一字段的多个单选按钮吗?如果他们做到了,他们打算如何编码?

编辑:

经过进一步研究,以下代码是我发现编写单选按钮的最佳方法。此代码假定我已经为我的每个单选选项定义了一个枚举。

@Html.RadioButtonFor(m => m.Gender, Gender.Male, new { id = "gender-male" })
@Html.LabelFor(m => m.Gender, Gender.Male.ToString(), new { @for = "gender-male" })
@Html.RadioButtonFor(m => m.Gender, Gender.Female, new { id = "gender-female" })
@Html.LabelFor(m => m.Gender, Gender.Female.ToString(), new { @for = "gender-female" })

也许这就是我可以期望的最好结果了,但它仍然让我有一点困扰。

  1. 为什么基本的东西如IDs需要这么多自定义呢?再次问,微软难道没有预见到我们想要将多个单选按钮与同一个字段相关联吗?

  2. 如果枚举名称与我想要用于单选按钮标签的描述不同,那么这种方法似乎不支持字段属性,如[Display(Name =“”)]

(我猜正确处理多个复选框将推迟到另一个问题。)


在我看来,RadioButtonFor 应该像 DropDownListFor 一样有重载方法,我可以传递一个项目列表,每个项目都会生成可点击标签的单选按钮。 - th1rdey3
@th1rdey3:我不确定DropDownListFor。也许是RadioButtonListFor?如果您想使用第三方选项,已经编写了这样的组件。但我不明白为什么微软认为它不需要。 - Jonathan Wood
现在你正在独自说话...关于你找到的解决方案,我认为那只是暂时的。 - Sxntk
@JonathanWood 顺便问一下,你在1月1日的凌晨2:56在SO上干了什么?为什么不庆祝新年呢? :) - Sergio
@jbutler483:感谢您提供另一个问题的链接。虽然没有完全回答我的问题,但那里有一些很好的信息。 - Jonathan Wood
显示剩余3条评论
2个回答

6

好的,让我们做一些研究。首先,您不需要唯一的id来通过单击标签切换复选框/单选按钮。实际上,您根本不需要id!您只需将输入包装在<label>标记内即可:

<label>
    <input type="radio" name="radio" value="1" /> Radio 1   
</label>

到目前为止,ASP.NET MVC已经为您提供了编写自己的HTML Helper的能力,让我们为枚举(Enum)模型字段编写一个HTML Helper! 假设我们有一些注册模型:
public class RegisterViewModel
{
    [Required]
    [EmailAddress]
    [Display(Name = "Email")]
    public string Email { get; set; }

    [Required]
    [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm password")]
    [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; } 
}

天哪!我刚刚复制粘贴了教程模型 o_O。现在我们想要获取用户的性别,所以我们添加一个 Gender 枚举:

public enum Gender
{
    Iamparanoic = 0,

    Male = 1,

    Female = 2,
}

我们需要在模型中添加一个Gender类型的Gender字段(希望C#有一个Gender访问修饰符!)

public Gender Gender { get; set; }

现在,是时候运用一些asp.net的魔法来编写我们的html辅助程序了:
public static MvcHtmlString RadioButtonsListForEnum<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression)
{
    var sb = new StringBuilder();
    sb.Append("<ul>"); //we want it to look cool, dont' we?
    foreach (var value in Enum.GetValues(typeof(TEnum)))
    {
        var radio = htmlHelper.RadioButtonFor(expression, value).ToHtmlString(); //you can generage any id and pass it to helper, or use a tagbuilder

        sb.AppendFormat(
            "<li><label>{0} {1}</label></li>",
            radio,
            ((Enum)value).GetEnumName() //instead of it you can grab e.g. Display/Description attribute value or w/e else
        );
    }
    sb.Append("</ul");
    return MvcHtmlString.Create(sb.ToString());
}

使用的方式如下:

@Html.RadioButtonsListForEnum(m => m.Gender)

太漂亮了,不是吗?当然,您可以添加很多自定义内容,比如使用独特的mvc样式ID渲染单选按钮,各种HTML属性等等。但这只是一个示例,是正确路径的一记踢屁股。

接下来,我们还想呈现复选框列表!

public static MvcHtmlString CheckBoxListForEnum<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, IDictionary<string, object> htmlAttributes = null)
{
    var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
    var enumValue = Convert.ToInt32(metadata.Model);

    var sb = new StringBuilder();
    foreach (var value in Enum.GetValues(typeof (TEnum)))
    {
        var val = Convert.ToInt32(value);
        var checkbox = new TagBuilder("input");

        checkbox.MergeAttribute("type", "checkbox");
        checkbox.MergeAttribute("value", value.ToString());
        checkbox.MergeAttribute("name", htmlHelper.NameFor(expression).ToString());

        if ((enumValue & val) == val)
            checkbox.MergeAttribute("checked", "checked");
        if (htmlAttributes != null)
            checkbox.MergeAttributes(htmlAttributes);

        var label = new TagBuilder("label") { InnerHtml = checkbox + ((Enum)value).GetEnumName() };

        sb.Append(label);
        sb.Append("<br/>");
    }
    return new MvcHtmlString(sb.ToString());
}

再次,简单用法:

@Html.CheckBoxListForEnum(m => m.Gender)

顺便说一下,只有带有Flags特性的枚举才有意义,并且在模型绑定时会遇到麻烦——需要自定义绑定器。但这是另一个问题,我希望Jon Skeet能回答它:)


1
对于在<label>元素内包含复选框的讨论,加1。这种方法不需要唯一ID。我熟悉这种方法,但曾经读到它不被推荐。然而,现在研究后发现,两种方法都是同样有效的。关于自定义HtmlHelper,我已经编写了一个相当复杂的,正如我在这里的讨论中所指出的。如果可用,我的版本还将使用枚举值的任何[Description]属性。我的复选框版本还支持初始选择值列表。 - Jonathan Wood
我仍然完全不明白为什么 MVC 没有包含对此的基本支持。事实上,即使使用您在@Html.CheckBoxFor()中包装<Labe>元素的技术也是无效的,因为它会生成重复的 ID。即使这样可以工作,也是不好的形式。 - Jonathan Wood
@JonathanWood 实际上我注意到这只是一个示例。希望您能够通过使用模型元数据和当前枚举值来生成唯一的ID。此外,您可以使用htmlattributes自定义助手程序,包装/不包装到ul,以满足您的需求。 - Sergio
为什么MVC中没有对它进行基本支持?因为大多数情况下人们并不需要它吗?如果您真的需要,您可以像自定义下拉列表、特殊标签等一样编写它自己的代码... - Sergio

1
在我看来,我认为微软方面不需要采取任何措施来处理您所谈论的情况。
从HTML代码开始,多选框在HTML中如何呈现?
<form action="">
<input type="radio" name="sex" value="male">Male<br>
<input type="radio" name="sex" value="female">Female
</form>

以上是我从W3cSchools带来的示例。

如果您想通过单击标签切换单选按钮,可以通过两种方式在html中实现此操作。

第一种方法是在标签中使用for标记。

<input type="radio" id="myId" name="MyName" value="MyValue" />
<label for="myId">Display Name</label>

第二种方法是将单选框输入放在label标签内。
<label>
   <input type="radio" id="myId" name="MyName" value="MyValue"/> 
   Display Name
</label>

正如您所看到的,这是一些标签的组合,以便在单击标签时获取单选按钮切换状态,我认为微软保持了简单和灵活,让开发人员选择自己喜欢的方式。

现在,在Mvc中获得相同的结果,您可以在上述两种方法中硬编码每个单选按钮,或者更加简便的方法是编写自己的Mvc Html扩展程序,我建议使用第二种方法。

希望这能帮助到您。


2
有了这种心态,就没有理由不使用MVC的CheckboxForLabelFor或其他HTML助手。微软认为它们是有价值的,许多人正在使用它们。显然,它们是有用的。在这里,支持只是稍微有点缺乏。 - Jonathan Wood
1
我同意你的观点,如果我们将这个想法概括一下,我肯定会得出与你所写的相同的结果,但在你的情况下,有两个想法,另一个程序希望以不同于你的方式获得结果,他不希望在用户单击标签时切换复选框,而你希望在用户单击标签时切换复选框,为了简单起见,正如你所知,这是mvc框架的基础,微软将提供基本的东西,这并不是缺乏,我认为。 - Monah
我同意@HadiHassan的观点,微软提供了基本控件,在winforms应用程序中,我们只能使用不支持所有需求的控件,因此,我们被迫购买第三方工具。 - Monah

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接