How can I make Html.CheckBoxFor() work on a string field?

jrizzo picture jrizzo · Aug 25, 2011 · Viewed 25.3k times · Source

I'm using ASP.NET MVC3 with Razor and C#. I am making a form builder of sorts, so I have a model that has a collection of the following object:

public class MyFormField
{
    public string Name { get; set; }
    public string Value { get; set; }
    public MyFormType Type { get; set; }
}

MyFormType is just an enum that tells me if the form field is a checkbox, or textbox, or file upload, or whatever. My editor template looks something like this (see the comment):

~/Views/EditorTemplates/MyFormField.cshtml

@model MyFormField
@{
    switch (Model.Type)
    {
        case MyFormType.Textbox:
            @Html.TextBoxFor(m => m.Value)
        case MyFormType.Checkbox:
            @Html.CheckBoxFor(m => m.Value)  // This does not work!
    }
}

I tried casting/converting the m.Value to a bool in the lambda expression for CheckBoxFor(), but that threw an error. I would just manually construct a checkbox input, but CheckBoxFor() seems to do two things that I can't seem to replicate:

  1. Creates a hidden input that somehow gets populated by the checkbox. This appears to be what the model binder picks up.
  2. Generates the name form the object so that the model binder gets the value into the right property.

Does anyone know a way around using CheckBoxFor() on a string, or a way to replicate its functionality manually, so that I can make this work?

Answer

mdm20 picture mdm20 · Aug 25, 2011

One way is to create your own htmlhelper extension method.

    public static MvcHtmlString CheckBoxStringFor<TModel>(this HtmlHelper<TModel> html, Expression<Func<TModel, string>> expression)
    {
        // get the name of the property
        string[] propertyNameParts = expression.Body.ToString().Split('.');
        string propertyName = propertyNameParts.Last();

        // get the value of the property
        Func<TModel, string> compiled = expression.Compile();
        string booleanStr = compiled(html.ViewData.Model);

        // convert it to a boolean
        bool isChecked = false;
        Boolean.TryParse(booleanStr, out isChecked);

        TagBuilder checkbox = new TagBuilder("input");
        checkbox.MergeAttribute("id", propertyName);
        checkbox.MergeAttribute("name", propertyName);
        checkbox.MergeAttribute("type", "checkbox");
        checkbox.MergeAttribute("value", "true");
        if (isChecked)
            checkbox.MergeAttribute("checked", "checked");

        TagBuilder hidden = new TagBuilder("input");
        hidden.MergeAttribute("name", propertyName);
        hidden.MergeAttribute("type", "hidden");
        hidden.MergeAttribute("value", "false");

        return MvcHtmlString.Create(checkbox.ToString(TagRenderMode.SelfClosing) + hidden.ToString(TagRenderMode.SelfClosing));
    }

The usage is the same as CheckBoxFor helper (e.Value is a string)

@Html.CheckBoxStringFor(e => e.Value)