【问题标题】:Asp .net mvc 3 CheckBoxFor method outputs hidden field, that hidden field value is false when checkbox is disabled with selected trueAsp .net mvc 3 CheckBoxFor 方法输出隐藏字段,当复选框被禁用并选中 true 时,该隐藏字段值为 false
【发布时间】:2012-06-13 08:50:46
【问题描述】:

CheckBoxFor(t => t.boolValue, new { disabled="disabled" }) 方法以在禁用模式下呈现复选框。

该方法还呈现一个隐藏字段。

我的问题是为什么这个隐藏字段的禁用复选框的值是错误的? 我相信隐藏字段的目的是在默认复选框行为之外有一些额外的行为

有没有办法覆盖默认的 MVC 功能,以便这个隐藏字段的值 即使在禁用模式下也是基于复选框的状态?

【问题讨论】:

  • 我提供了一个解决方案,它可以通过传递一个额外的参数来使用没有隐藏字段的复选框。

标签: asp.net-mvc checkboxfor


【解决方案1】:

隐藏字段用于将复选框值绑定到布尔属性。问题是,如果未选中复选框,则不会向服务器发送任何内容,因此 ASP.NET MVC 使用此隐藏字段发送 false 并绑定到相应的布尔字段。除了编写自定义帮助程序之外,您无法修改此行为。

话虽如此,不要在复选框上使用disabled="disabled",而是使用readonly="readonly"。这样,您将保持相同的期望行为,即用户无法修改其值,但除此之外,它的值将在提交表单时发送到服务器:

@Html.CheckBoxFor(x => x.Something, new { @readonly = "readonly" })

更新:

正如 cmets 部分所指出的,readonly 属性不适用于 Google Chrome。另一种可能性是使用另一个隐藏字段并禁用复选框:

@Html.HiddenFor(x => x.Something)
@Html.CheckBoxFor(x => x.Something, new { disabled = "disabled" })

更新 2:

这是一个带有附加隐藏字段的完整测试用例。

型号:

public class MyViewModel
{
    public bool Foo { get; set; }
}

控制器:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View(new MyViewModel { Foo = true });
    }

    [HttpPost]
    public ActionResult Index(MyViewModel model)
    {
        return Content(model.Foo.ToString());
    }
}

查看:

@model MyViewModel

@using (Html.BeginForm())
{
    @Html.HiddenFor(x => x.Foo)
    @Html.CheckBoxFor(x => x.Foo, new { disabled = "disabled" })
    <button type="submit">OK</button>
}

提交表单时,Foo 属性的值为true。我已经在所有主流浏览器(Chrome、FF、IE)上进行了测试。

【讨论】:

  • 嗯,只读的东西不适用于复选框。至少镀铬。是的,您对未选中复选框需要隐藏字段是正确的。我的愿望是可以使用相同的隐藏字段来处理禁用的部分,考虑到 readonly 不起作用。你试过用哪个浏览器只读?
  • @Shameet,哦,是的,你完全正确。 Readonly 不适用于复选框。我已经用另一个建议更新了我的答案。
  • 达林,具有相同属性名称的额外隐藏不起作用。模型绑定器仍将该属性设置为 false。拥有另一个属性(其隐藏)来处理禁用将起作用,但需要处理大量代码。我认为为布尔值编写自己的模板视图将是一个优雅的解决方案??
  • @Shameet,额外的隐藏字段对我有用。让我更新我的答案以提供我的完整测试用例。这当然可以外部化到自定义编辑器模板中,这样您在视图中所要做的就是:@Html.EditorFor(x =&gt; x.Foo)
  • AFAIK,这将生成多个具有相同名称的输入控件,并且它们都将被回发。模型活页夹只是拿起第一个吗?如果是,如果有条件地(仅有时)禁用复选框,该解决方案是否仍然有效?
【解决方案2】:

我发现查询字符串中添加的参数看起来很乱,让人们认为我们作为开发人员做错了什么。所以我采用了创建自己的 InputExtensions 类的极端方法,它允许我决定是否要呈现隐藏的输入。

以下是我创建的 InputExtensions 类(基于现有的 MVC 代码保持完全兼容):

public static class InputExtensions
{
    //
    // Checkboxes
    //

    public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, bool>> expression, bool renderHiddenInput)
    {
        if (renderHiddenInput)
        {
            return System.Web.Mvc.Html.InputExtensions.CheckBoxFor(htmlHelper, expression);
        }
        return CheckBoxFor(htmlHelper, expression, false);
    }

    public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, bool>> expression, object htmlAttributes, bool renderHiddenInput)
    {
        if (renderHiddenInput)
        {
            return System.Web.Mvc.Html.InputExtensions.CheckBoxFor(htmlHelper, expression, htmlAttributes);
        }
        return CheckBoxFor(htmlHelper, expression, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes), false);
    }

    public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, bool>> expression, IDictionary<string, object> htmlAttributes,
        bool renderHiddenInput)
    {
        if (renderHiddenInput)
        {
            return System.Web.Mvc.Html.InputExtensions.CheckBoxFor(htmlHelper, expression, htmlAttributes);
        }

        if (expression == null)
        {
            throw new ArgumentNullException("expression");
        }

        ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
        bool? isChecked = null;
        if (metadata.Model != null)
        {
            bool modelChecked;
            if (Boolean.TryParse(metadata.Model.ToString(), out modelChecked))
            {
                isChecked = modelChecked;
            }
        }

        return CheckBoxHelper(htmlHelper, metadata, ExpressionHelper.GetExpressionText(expression), isChecked, htmlAttributes);
    }

    private static MvcHtmlString CheckBoxHelper(HtmlHelper htmlHelper, ModelMetadata metadata, string name, bool? isChecked, IDictionary<string, object> htmlAttributes)
    {
        RouteValueDictionary attributes = ToRouteValueDictionary(htmlAttributes);

        bool explicitValue = isChecked.HasValue;
        if (explicitValue)
        {
            attributes.Remove("checked"); // Explicit value must override dictionary
        }

        return InputHelper(htmlHelper,
                           InputType.CheckBox,
                           metadata,
                           name,
                           value: "true",
                           useViewData: !explicitValue,
                           isChecked: isChecked ?? false,
                           setId: true,
                           isExplicitValue: false,
                           format: null,
                           htmlAttributes: attributes);
    }

    //
    // Helper methods
    //

    private static MvcHtmlString InputHelper(HtmlHelper htmlHelper, InputType inputType, ModelMetadata metadata, string name, object value, bool useViewData, bool isChecked, bool setId, bool isExplicitValue, string format, IDictionary<string, object> htmlAttributes)
    {
        string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
        if (string.IsNullOrEmpty(fullName))
        {
            throw new ArgumentException("Value cannot be null or empty.", "name");
        }

        var tagBuilder = new TagBuilder("input");
        tagBuilder.MergeAttributes(htmlAttributes);
        tagBuilder.MergeAttribute("type", HtmlHelper.GetInputTypeString(inputType));
        tagBuilder.MergeAttribute("name", fullName, true);

        string valueParameter = htmlHelper.FormatValue(value, format);
        var usedModelState = false;

        bool? modelStateWasChecked = GetModelStateValue(htmlHelper.ViewData, fullName, typeof(bool)) as bool?;
        if (modelStateWasChecked.HasValue)
        {
            isChecked = modelStateWasChecked.Value;
            usedModelState = true;
        }
        if (!usedModelState)
        {
            string modelStateValue = GetModelStateValue(htmlHelper.ViewData, fullName, typeof(string)) as string;
            if (modelStateValue != null)
            {
                isChecked = string.Equals(modelStateValue, valueParameter, StringComparison.Ordinal);
                usedModelState = true;
            }
        }
        if (!usedModelState && useViewData)
        {
            isChecked = EvalBoolean(htmlHelper.ViewData, fullName);
        }
        if (isChecked)
        {
            tagBuilder.MergeAttribute("checked", "checked");
        }
        tagBuilder.MergeAttribute("value", valueParameter, isExplicitValue);

        if (setId)
        {
            tagBuilder.GenerateId(fullName);
        }

        // If there are any errors for a named field, we add the css attribute.
        ModelState modelState;
        if (htmlHelper.ViewData.ModelState.TryGetValue(fullName, out modelState))
        {
            if (modelState.Errors.Count > 0)
            {
                tagBuilder.AddCssClass(HtmlHelper.ValidationInputCssClassName);
            }
        }

        tagBuilder.MergeAttributes(htmlHelper.GetUnobtrusiveValidationAttributes(name, metadata));

        return MvcHtmlString.Create(tagBuilder.ToString(TagRenderMode.SelfClosing));
    }

    private static RouteValueDictionary ToRouteValueDictionary(IDictionary<string, object> dictionary)
    {
        return dictionary == null ? new RouteValueDictionary() : new RouteValueDictionary(dictionary);
    }

    private static object GetModelStateValue(ViewDataDictionary viewData, string key, Type destinationType)
    {
        ModelState modelState;
        if (viewData.ModelState.TryGetValue(key, out modelState))
        {
            if (modelState.Value != null)
            {
                return modelState.Value.ConvertTo(destinationType, culture: null);
            }
        }
        return null;
    }

    private static bool EvalBoolean(ViewDataDictionary viewData, string key)
    {
        return Convert.ToBoolean(viewData.Eval(key), CultureInfo.InvariantCulture);
    }
}

然后你可以像这样调用方法:

@Html.CheckBoxFor(t => t.boolValue, new { disabled="disabled" }, false)

【讨论】:

  • 没有使用你的确切实现,但你的回答让我走上了自定义复选框助手的道路。
【解决方案3】:

这是我发现的一个快速提示:如果您为对象上的布尔值设置默认值。可以写出值为真/假的标签(该值应与对象上的默认布尔值相反)

<input type="checkbox" name="IsAllDayEvent" value="true" id="IsAllDayEvent" />

该值仅在复选框被选中时发送,所以它有效。

【讨论】:

    【解决方案4】:

    虽然达林的回答非常好,但还有另一种方法可以确保禁用的复选框按预期回发。

    此解决方案也适用于基于模型中的属性有条件地禁用复选框,或根据用户选择或客户端检索数据启用/禁用客户端的情况。

    您可以保持视图简单:(此处禁用设置是可选的)

    @Html.CheckBoxFor(m => m.Something, new { id = "somethingCheckbox", disabled = "disabled" })
    

    正如 Darin 所提到的,这将生成一个额外的隐藏输入字段,设置为 false,这样复选框值也会在未选中复选框时回发。因此,我们知道Something 字段将始终回发,无论复选框是否选中、禁用与否。唯一的问题是,如果复选框被禁用,它将始终以False 发回。

    我们可以在提交之前通过一些 JavaScript 后处理来解决这个问题。在我的例子中,代码看起来是这样的:

    var dataToPost = $form.serializeArray();
    
    if (dataToPost != null) {
        for (var i = 0; i < dataToPost.length; i++) {
            if (dataToPost[i].name === "Something") {
                dataToPost[i].value = $("#somethingCheckbox").attr("checked") === "checked";
                }
            }
        }  
    $.post($form.attr("action"), dataToPost)
    

    您可能需要针对您的场景对其进行修改,但您应该明白这一点。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-05-27
      • 2016-09-06
      • 2012-12-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-05
      • 2017-11-23
      相关资源
      最近更新 更多