0

I am trying to translate this razor code to C# code. However, having problem with checkbox postbacks.

<div class="input-group">
   @Html.TextBoxFor(model => model.Property1, new { @class = "form-control js-template-text", data_val = "false" }
   <span class="input-group-addon">
       @Html.CheckBoxFor(model => model.Property2, new { data_val = "false" })
       @Html.HiddenFor(model => model.Property2)
       <span class="glyphicon glyphicon-phone glyphicon-green"></span>
   </span>
</div>

This bit of the markup works just fine. But the markup generated from this c# code does not. The generated markup from both approaches are the same. My question is: what modification (if any at all) is needed in the code, so that browser should send the correct checkbox value.

private static MvcHtmlString CreateTextBoxCheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, string>> textBoxExpression, Expression<Func<TModel, bool>> checkBoxExpression, IGlyphIcon checkBoxGlyphIcon = null, IDictionary<string, object> htmlAttributes = null, IDictionary<string, object> textBoxHtmlAttributes = null, IDictionary<string, object> checkBoxHtmlAttributes = null)
{
    var sb = new StringBuilder();

    #region OuterDiv

    var outerDivTag = new TagBuilder("div");
    outerDivTag.MergeAttributes(htmlAttributes);
    outerDivTag.AddCssClass("input-group");
    sb.AppendLine(outerDivTag.ToString(TagRenderMode.StartTag));

    #endregion OuterDiv

    #region TextBox

    var textBoxName = ExpressionHelper.GetExpressionText(textBoxExpression);
    var textBoxFullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(textBoxName);
    var textBoxId = TagBuilder.CreateSanitizedId(textBoxFullName);

    var textBoxMetaData = ModelMetadata.FromLambdaExpression(textBoxExpression, htmlHelper.ViewData);
    var textBoxValue = textBoxMetaData.Model.ToString();

    var textBoxTag = new TagBuilder("input");
    textBoxTag.Attributes.Add("type", "text");
    textBoxTag.Attributes.Add("class", "form-control");
    textBoxTag.Attributes.Add("name", textBoxFullName);
    textBoxTag.Attributes.Add("id", textBoxId);
    textBoxTag.Attributes.Add("value", textBoxValue);

    // get data annotation/client side scripts attributes
    var textkBoxValidationAttributes = htmlHelper.GetUnobtrusiveValidationAttributes(textBoxFullName, textBoxMetaData);
    foreach (var key in textkBoxValidationAttributes.Keys)
    {
        textBoxTag.Attributes.Add(key, textkBoxValidationAttributes[key].ToString());
    }

    textBoxTag.MergeAttributes(textBoxHtmlAttributes, true);
    sb.AppendLine(textBoxTag.ToString(TagRenderMode.SelfClosing));

    #endregion TextBox

    #region CheckBox

    var checkBoxSpan = new TagBuilder("span");
    checkBoxSpan.Attributes.Add("class", "input-group-addon");

    var checkBoxName = ExpressionHelper.GetExpressionText(checkBoxExpression);
    var checkBoxFullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(checkBoxName);
    var checkBoxId = TagBuilder.CreateSanitizedId(checkBoxFullName);

    var checkBoxMetaData = ModelMetadata.FromLambdaExpression(checkBoxExpression, htmlHelper.ViewData);
    var checkBoxValue = checkBoxMetaData.Model.ToString().ToLower();

    var checkBoxTag = new TagBuilder("input");
    checkBoxTag.Attributes.Add("type", "checkbox");
    checkBoxTag.Attributes.Add("name", checkBoxFullName);
    checkBoxTag.Attributes.Add("id", checkBoxId);
    checkBoxTag.Attributes.Add("value", checkBoxValue);
    if (checkBoxValue.Equals("true", StringComparison.InvariantCultureIgnoreCase))
    {
        checkBoxTag.Attributes.Add("checked", "checked");
    }

    // get data annotation/client side scripts attributes
    var validationAttributes = htmlHelper.GetUnobtrusiveValidationAttributes(checkBoxFullName, checkBoxMetaData);
    foreach (var key in validationAttributes.Keys)
    {
        checkBoxTag.Attributes.Add(key, validationAttributes[key].ToString());
    }

    checkBoxTag.MergeAttributes(checkBoxHtmlAttributes, true);

    // to keep track of checkbox postbacks, create hidden input for checkbox
    var checkBoxHiddenTag = new TagBuilder("input");
    checkBoxHiddenTag.Attributes.Add("name", checkBoxFullName);
    checkBoxHiddenTag.Attributes.Add("id", checkBoxId);
    checkBoxHiddenTag.Attributes.Add("type", "hidden");
    checkBoxHiddenTag.Attributes.Add("value", checkBoxValue.Equals("true", StringComparison.InvariantCultureIgnoreCase) ? "True" : "False");

    sb.AppendLine(checkBoxSpan.ToString(TagRenderMode.StartTag));
    sb.AppendLine(checkBoxTag.ToString(TagRenderMode.SelfClosing));
    sb.AppendLine(checkBoxHiddenTag.ToString(TagRenderMode.SelfClosing));

    #endregion CheckBox

    #region GlyphIcon

    if (checkBoxGlyphIcon != null)
    {
        var checkBoxGlyphIconSpan = new TagBuilder("span");
        checkBoxGlyphIconSpan.Attributes.Add("class", checkBoxGlyphIcon.CssClass);
        sb.AppendLine(checkBoxGlyphIconSpan.ToString(TagRenderMode.StartTag));
        sb.AppendLine(checkBoxGlyphIconSpan.ToString(TagRenderMode.EndTag));
    }

    #endregion GlyphIcon

    sb.AppendLine(checkBoxSpan.ToString(TagRenderMode.EndTag));
    sb.AppendLine(outerDivTag.ToString(TagRenderMode.EndTag));
    var result = MvcHtmlString.Create(sb.ToString());
    return result;
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
  • What is your question. What problem are you having? –  Jul 31 '15 at 11:07
  • And is there any reason why you don't just use the built-in extensions instead of trying to reinvent the wheel - e.g. `sb.append(htmlHelper.TextBoxFor(....));` etc. –  Jul 31 '15 at 11:11
  • @StephenMuecke, the problem is the browser does not postback the checkbox value (correct) using the extension method approach, but it does using the razor markup. – user2937874 Jul 31 '15 at 12:57
  • @StephenMuecke And as why not using the existing framework extensions, tried that first but it somehow generated the markup with extra characters, which stop the form from submitting. It took awhile to figure that out. – user2937874 Jul 31 '15 at 12:59
  • I know, If the initial value is `true` it wlll always post back `true` even of you un-check the checkbox :). But you need to explain the issue in your question (not expect others to guess the problem). –  Jul 31 '15 at 13:00
  • Maybe this will help: https://stackoverflow.com/a/56256649/5836671. I wrote a helper for the extra hidden input problem. – VDWWD May 22 '19 at 12:24

1 Answers1

0

Firstly your view code contains @Html.HiddenFor(model => model.Property2) which is a bit pointless and should be deleted. The CheckBoxFor() method correctly renders a hidden input for the property with a value of False. Fortunately this input is ignored by the DefaultModelBinder (otherwise you would only ever post back the original value of the property).

Your claim that "The generated markup from both approaches are the same" is not true and your code is generating a checkbox and a hidden input where the value of the hidden input id the initial value of the property because of your use of

checkBoxHiddenTag.Attributes.Add("value", checkBoxValue.Equals("true", StringComparison.InvariantCultureIgnoreCase) ? "True" : "False");

This means if

  1. The initial value is false and the checkbox is checked you post back True and False resulting in true (the model binder only uses the first value)
  2. The initial value is false and checkbox is un-checked you post back False resulting in false
  3. The initial value is true and the checkbox is checked you post back True and False resulting in true
  4. The initial value is true and the checkbox is un-checked you post back True resulting in true (which is where your problem is)

There are also numerous other issues with you code, including that your not binding to the ModelState if you return the view because of validation errors.

If you want to do all this manually, I recommend you study the source code, but all this can be simplified by using the built in methods

StringBuilder html = new StringBuilder()
html.Append(htmlHelper.CheckBoxFor(checkBoxExpression, checkBoxHtmlAttributes).ToString());

if (checkBoxGlyphIcon != null)
{
    var checkBoxGlyphIconSpan = new TagBuilder("span");
    checkBoxGlyphIconSpan.Attributes.Add("class", checkBoxGlyphIcon.CssClass);
    html.Append(checkBoxGlyphIconSpan.ToString());
}

var checkBoxSpan = new TagBuilder("span");
checkBoxSpan.Attributes.Add("class", "input-group-addon");
checkBoxSpan.InnerHtml = html.ToString();

html = new StringBuilder();
html.Append(htmlHelper.TextBoxFor(textBoxExpression, textBoxHtmlAttributes).ToString());
html.Append(checkBoxSpan.ToString());

var outerDivTag = new TagBuilder("div");
outerDivTag.MergeAttributes(htmlAttributes);
outerDivTag.AddCssClass("input-group");
outerDivTag.InnerHtml = html.ToString();

MvcHtmlString.Create(outerDivTag.ToString());