4

I am using bootstrap 5 in a new project I am starting, and rather than having to write all the scaffolding around a form field, I have decided to create a wrapper to do this for me automatically.

I have used the following syntax for textboxfor, textareafor and dropdownlist:

public static MvcHtmlString MyTextBoxFor<TModel, TProperty>(
        this HtmlHelper<TModel> helper, 
        Expression<Func<TModel, TProperty>> expression)
    {
        var stringbuilder = new MvcHtmlString("<div class='form-group'>" +
                                                    helper.LabelFor(expression, new {@class = "col-sm-3 control-label"}) +
                                                    "<div class='col-sm-5'>" +
                                                        helper.TextBoxFor(expression, new {@class = "form-control"}) +
                                                    "</div>" +
                                              "</div>");

        return stringbuilder;
    }

Which can then be called as follows:

@FormHelpers.MyTextBoxFor(Html, x => x.Name)

However this does not appear to work for checkboxfor:

Error 1 'System.Web.Mvc.HtmlHelper<TModel>' does not contain a definition for 'CheckBoxFor' and the best extension method overload 'System.Web.Mvc.Html.InputExtensions.CheckBoxFor<TModel>(System.Web.Mvc.HtmlHelper<TModel>, System.Linq.Expressions.Expression<System.Func<TModel,bool>>)' has some invalid arguments

If I change TProperty to bool it will then compile, but I get a runtime error on the line where I call this helper:

CS0411: The type arguments for method 'CollectionSystem.Helpers.FormHelpers.MyCheckboxFor<TModel,TProperty>(System.Web.Mvc.HtmlHelper<TModel>, System.Linq.Expressions.Expression<System.Func<TModel,bool>>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

Can someone advise how I can go about wrapping the CheckboxFor function please.

Gavin Coates
  • 1,366
  • 1
  • 20
  • 44

2 Answers2

10

Stop and remove all this code from your project immediately.

What you want are editor templates. First, create a new folder at Views\Shared\EditorTemplates. Then, in that folder, create views named after either a type or one of the members of the DataType enum. For example:

Views\Shared\EditorTemplates\String.cshtml

<div class="form-control">
    @Html.Label("", new { @class = "col-sm-3 control-label" })
    <div class="col-sm-5">
        @Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue.ToString(), new { @class = "form-control" })
    </div>
</div>

Or for your checkbox scenario, for example:

Views\Shared\EditorTemplates\Boolean.cshtml

@model Boolean?
<div class="form-control">
    <div class="col-offset-sm-3 col-sm-5">
        <div class="checkbox">
            <label>
                @Html.CheckBox("", Model ?? false)
                @ViewData.ModelMetadata.GetDisplayName() 
            </label>
        </div>
    </div>
</div>

Rinse and repeat with anything else you need. Then in your views, you just need to do:

@Html.EditorFor(m => m.TheProperty)

And based on the type of the property or what DataType it's decorated with, the right editor template will be used, and no custom helpers that developers have to remember to use.

I have a more in depth explanation on my blog here:

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • Thanks Chris. I am just reading your blog. I found typo in thesection: "Sounds simple enough. What's the catch?", first paragraph, third line, "...a model for thse views..." – Celdor Feb 07 '15 at 02:49
  • @Chris Pratt what about using both Checkboxes and Radiobuttons with EditorTemplates ? – T-moty May 04 '15 at 18:56
  • Radios aren't typically utilized for a `bool` type, unless you literally want a radio for both the true and false values. Still, if you want to be able to handle the same type, `bool` in this case, with either one, you'll need some way to differentiate. That could be done a number of ways. You can use something like `[UIHint("Radio")]` on your property to specify that a `Radio.cshtml` editor template should be used. Or you could pass some value into the templates `ViewData` and branch on that: `Html.EditorFor(m => m.SomeBool, new { useRadios = true })`, then branch on `ViewData["useRadios"]` – Chris Pratt May 04 '15 at 19:09
4

You helper would need to be like

public static MvcHtmlString BootstrapCheckBoxFor<TModel>(this HtmlHelper<TModel> helper, Expression<Func<TModel, bool>> expression)
{

  TagBuilder innerContainer = new TagBuilder("div");
  innerContainer.AddCssClass("col-sm-5");
  innerContainer.InnerHtml = helper.CheckBoxFor(expression, new {@class = "form-control"}).ToString();

  StringBuilder html = new StringBuilder();
  html.Append(helper.LabelFor(expression, new {@class = "col-sm-3 control-label"}));
  html.Append(innerContainer.ToString());

  TagBuilder outerContainer = new TagBuilder("div");
  outerContainer.AddCssClass("form-group");
  outerContainer.InnerHtml = html.ToString();

  return MvcHtmlString.Create(outerContainer.ToString());

}

Note you may want to edit this to include @Html.ValidationMessageFor()