Bootstrap MVC HTML Input Extensions

Bootstrap C# Website MVC
Bootstrap MVC HTML Input Extensions

In MVC you can easily create a TextBox for a Property with Html.TextBoxFor().
This generates <input id="" name="" type="text" value="" />

But if you want extra properties like class or maxlength you must add them yourself with Html.TextBoxFor(m => m.Prop, new { @class = "form-control", maxlength = 50 }).

You need to do this for every control if you want to use the Bootstrap classes. But you can add an Extension that automatically does all this for you. The demo project has 11 extensions for Bootstrap 4 & 5.

  • BootstrapDropDownListFor
  • BootstrapFileUploadFor
  • BootstrapLabelFor
  • BootstrapRadioButtonFor
  • BootstrapSwitchFor
  • BootstrapTextAreaFor
  • BootstrapTextBoxFor
  • BootstrapToolTip
  • BootstrapValidationMessageFor
  • BootstrapCheckBoxFor
  • BootstrapDateTextBoxFor

View source on GitHub

How it works

Start by Creating a Folder in your Project called Extensions. Add the Extensions you want from the Project on GitHub. But you will also need the code file Helpers.cs. And for frontend jQuery validation for the CheckBox and FileUpload you will need some code from scripts.js.

Below is the code for the BootstrapTextBoxFor extension. It uses the Attribute [StringLength(50)] of the Property to create the maxlength property in HTML. The name of the Property can determine the type="xxx" property. So for example the Property is named "Phone" or "Number" the type will be type="tel", when the name contains "search" the type will be type="search".

In the HTML examples below this code snippet are the written out HTML results for the original and then the Extension so you can see what it does and with which Property. There are examples for Bootstrap 4 and 5 because the class names are different to create HTML controls like CheckBox and Switch.


using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Web.Mvc;
using System.Web.Routing;

namespace YourNameSpace
{
    public static partial class HtmlHelperExtensions
    {
        public static MvcHtmlString BootstrapTextBoxFor<TModel, TValue>(this HtmlHelper<TModel> htmlHelper, 
            Expression<Func<TModel, TValue>> expression, object htmlAttributes = null)
        {
            //get the data from the model binding
            var fieldName = ExpressionHelper.GetExpressionText(expression);
            var fullBindingName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(fieldName);
            var fieldId = TagBuilder.CreateSanitizedId(fullBindingName);
            var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
            var modelValue = metadata.Model;
            var fieldNameLowered = fieldName.ToLower();

            //create the textbox
            var textbox = new TagBuilder("input");

            //are there attributes
            textbox.MergeAttributes(new RouteValueDictionary(FixHtmlAttributes(htmlAttributes)));

            //add the attributes if they are not already in htmlAttributes
            textbox.Attributes.Add("name", fullBindingName);
            if (!textbox.Attributes.Any(x => x.Key.ToLower() == "value") && modelValue != null)
            {
                textbox.Attributes.Add("value", modelValue.ToString());
            }
            if (!textbox.Attributes.Any(x => x.Key.ToLower() == "id"))
            {
                textbox.Attributes.Add("id", fieldId);
            }
            if (!textbox.Attributes.Any(x => x.Key.ToLower() == "autocomplete"))
            {
                textbox.Attributes.Add("autocomplete", "off");
            }
            if (!textbox.Attributes.Any(x => x.Key.ToLower() == "class"))
            {
                textbox.Attributes.Add("class", BootstrapHelper.DefaultClassName);
            }
            else
            {
                textbox.Attributes["class"] = $"{BootstrapHelper.DefaultClassName} {textbox.Attributes["class"]}";
            }

            //determine the textbox type based on it's name. You can add your own common names to get the correct type
            if (!textbox.Attributes.Any(x => x.Key.ToLower() == "type"))
            {
                string type = "text";

                if (fieldNameLowered.Contains("password"))
                {
                    type = "password";
                }
                else if (fieldNameLowered.Contains("e_mail") || fieldNameLowered.Contains("email"))
                {
                    type = "email";
                }
                else if (fieldNameLowered.Contains("phone") || fieldNameLowered.Contains("mobile") || 
                         fieldNameLowered.Contains("number") || fieldNameLowered.Contains("amount"))
                {
                    type = "tel";
                }
                else if (fieldNameLowered.Contains("search"))
                {
                    type = "search";
                }
                else if (fieldNameLowered.Contains("url") || fieldNameLowered.Contains("website"))
                {
                    type = "url";
                }

                textbox.Attributes.Add("type", type);
            }

            //find the maxlengt from the StringLength attribute
            if (!textbox.Attributes.Any(x => x.Key.ToLower() == "maxlength"))
            {
                int maxLength = 250;
                var member = expression.Body as MemberExpression;

                if (member?.Member != null)
                {
                    var stringLength = member.Member.GetCustomAttributes(typeof(StringLengthAttribute), false).FirstOrDefault();

                    if (stringLength != null)
                    {
                        maxLength = ((StringLengthAttribute)stringLength).MaximumLength;
                    }
                    else
                    {
                        //indien geen string lengte probeer dan maxlengt te bepalen door lenge van grootste nummer in range
                        var stringLengthRange = member.Member.GetCustomAttributes(typeof(RangeAttribute), false).FirstOrDefault();

                        if (stringLengthRange != null)
                        {
                            maxLength = ((RangeAttribute)stringLengthRange).Maximum.ToString().Length;
                        }
                    }
                }

                textbox.Attributes.Add("maxlength", maxLength.ToString());
            }

            //add the validation
            textbox.MergeAttributes(htmlHelper.GetUnobtrusiveValidationAttributes(fieldName));

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

Bootstrap 4

[StringLength(50)]
@Html.TextBoxFor(m => m.Prop)
<input id="Prop" name="Prop" type="text" value="{VALUE}" />
[StringLength(50)]
@Html.BootstrapTextBoxFor(m => m.Prop)
<input id="Prop" name="Prop" autocomplete="off" class="form-control" maxlength="50" type="text" value="{VALUE}" />

[StringLength(10)]
@Html.TextBoxFor(m => m.Prop)
<input id="Prop" name="Prop" type="text" value="{VALUE}" />
[StringLength(10)]
@Html.BootstrapDateTextBoxFor(m => m.Prop)
<input id="Prop" name="Prop" autocomplete="off" class="form-control bootstrap-datepicker" maxlength="10" placeholder="dd/MM/yyyy" type="text" value="{VALUE}" />

@Html.TextBoxFor(m => m.Prop, new { type = "file" })
<input id="Prop" name="Prop" type="file" />
[FileType("bmp,gif,jpg,jpeg,png")]
@Html.BootstrapFileUploadFor(m => m.Prop)
<div class="custom-file">
    <input id="Prop" name="Prop" accept=".bmp,.gif,.jpg,.jpeg,.png" class="custom-file-input" type="file" />
    <label class="custom-file-label"></label>
</div>

@Html.CheckBoxFor(m => m.Prop)
<input id="Prop" name="Prop" type="checkbox" value="true" />
<input name="Prop" type="hidden" value="false" />
@Html.BootstrapCheckBoxFor(m => m.Prop)
<div class="custom-control custom-checkbox">
    <input id="Prop" name="Prop" type="checkbox" value="true" class="custom-control-input" />
    <input name="Prop" type="hidden" value="false" />
    <label class="custom-control-label fw-bold" for="Prop">
        {LABEL}
    </label>
</div>

@Html.BootstrapSwitchFor(m => m.Prop)
<div class="custom-control custom-switch">
    <input id="Prop" name="Prop" type="checkbox" value="true" class="custom-control-input" />
    <input name="Prop" type="hidden" value="false" />
    <label class="custom-control-label fw-bold" for="Prop">
        {LABEL}
    </label>
</div>

@Html.RadioButtonFor(m => m.Prop, Enum.Label)
<input id="Prop" name="Prop" type="radio" value="{LABEL}" />
@Html.BootstrapRadioButtonFor(m => m.Prop, Enum.Label)
<div class="custom-control custom-radio">
    <input id="Prop{LABEL}" name="Prop" type="radio" value="{LABEL} class="custom-control-input" />
    <label class="custom-control-label fw-bold" for="Prop{LABEL}">
        {LABEL}
    </label>
</div>

@Html.DropDownListFor(m => m.Prop, new SelectList(Enumerable.Range(1, 10)), "Select...")
<select id="Prop" name="Prop">
    <option value="">Select...</option>
    <option value="1">1</option>
</select>
@Html.BootstrapDropDownListFor(m => m.Prop, new SelectList(Enumerable.Range(1, 10)), "Select...")
<select id="Prop" name="Prop" class="form-control">
    <option value="">Select...</option>
    <option value="1">1</option>
</select>

@Html.LabelFor(m => m.Prop)
<label for="Prop">
    {LABEL}
</label>
@Html.BootstrapLabelFor(m => m.Prop)
<label for="Prop" class="fw-bold">
    {LABEL}
</label>
<span class="fw-bold text-danger">
    *
</span>

[StringLength(500)]
@Html.TextAreaFor(m => m.Prop)
<textarea id="Prop" name="Prop" cols="20" rows="2"> </textarea>
[StringLength(500)]
@Html.BootstrapTextAreaFor(m => m.Prop)
<textarea id="Prop" name="Prop" maxlength="500" class="form-control"></textarea>
<span class="textarea-remain">
    <span class="textarea-remain-value">
        500
    </span>
    characters remaining.
</span>

@Html.ValidationMessageFor(m => m.Prop)
<span class="field-validation-valid" data-valmsg-for="Prop" data-valmsg-replace="true"></span>
@Html.BootstrapValidationMessageFor(m => m.Prop)
<span class="field-validation-valid fw-bold text-danger small" data-valmsg-for="Prop" data-valmsg-replace="true"></span>

@Html.BootstrapToolTip("{TOOLTIP-TEXT}")
<span class="badge bg-primary" data-bs-toggle="tooltip" title="{tooltip-text}">
    ?
</span>

Bootstrap 5

[StringLength(50)]
@Html.TextBoxFor(m => m.Prop)
<input id="Prop" name="Prop" type="text" value="{VALUE}" />
[StringLength(50)]
@Html.BootstrapTextBoxFor(m => m.Prop)
<input id="Prop" name="Prop" autocomplete="off" class="form-control" maxlength="50" type="text" value="{VALUE}" />

[StringLength(10)]
@Html.TextBoxFor(m => m.Prop)
<input id="Prop" name="Prop" type="text" value="{VALUE}" />
[StringLength(10)]
@Html.BootstrapDateTextBoxFor(m => m.Prop)
<input id="Prop" name="Prop" autocomplete="off" class="form-control bootstrap-datepicker" maxlength="10" placeholder="dd/MM/yyyy" type="text" value="{VALUE}" />

@Html.TextBoxFor(m => m.Prop, new { type = "file" })
<input id="Prop" name="Prop" type="file" />
[FileType("bmp,gif,jpg,jpeg,png")]
@Html.BootstrapFileUploadFor(m => m.Prop)
<div>
    <input id="Prop" name="Prop" accept=".bmp,.gif,.jpg,.jpeg,.png" class="form-control" type="file" />
    <label></label>
</div>

@Html.CheckBoxFor(m => m.Prop)
<input id="Prop" name="Prop" type="checkbox" value="true" />
<input name="Prop" type="hidden" value="false" />
@Html.BootstrapCheckBoxFor(m => m.Prop)
<div class="form-check">
    <input id="Prop" name="Prop" type="checkbox" value="true" class="form-check-input" />
    <input name="Prop" type="hidden" value="false" />
    <label class="form-check-label fw-bold" for="Prop">
        {LABEL}
    </label>
</div>

@Html.BootstrapSwitchFor(m => m.Prop)
<div class="form-check">
    <input id="Prop" name="Prop" type="checkbox" value="true" class="form-check-input" />
    <input name="Prop" type="hidden" value="false" />
    <label class="form-check-label fw-bold" for="Prop">
        {LABEL}
    </label>
</div>

@Html.RadioButtonFor(m => m.Prop, Enum.Label)
<input id="Prop" name="Prop" type="radio" value="{LABEL}" />
@Html.BootstrapRadioButtonFor(m => m.Prop, Enum.Label)
<div class="form-check">
    <input id="Prop{LABEL}" name="Prop" type="radio" value="{LABEL} class="form-check-input" />
    <label class="form-check-label fw-bold" for="Prop{LABEL}">
        {LABEL}
    </label>
</div>

@Html.DropDownListFor(m => m.Prop, new SelectList(Enumerable.Range(1, 10)), "Select...")
<select id="Prop" name="Prop">
    <option value="">Select...</option>
    <option value="1">1</option>
</select>
@Html.BootstrapDropDownListFor(m => m.Prop, new SelectList(Enumerable.Range(1, 10)), "Select...")
<select id="Prop" name="Prop" class="form-control">
    <option value="">Select...</option>
    <option value="1">1</option>
</select>

@Html.LabelFor(m => m.Prop)
<label for="Prop">
    {LABEL}
</label>
@Html.BootstrapLabelFor(m => m.Prop)
<label for="Prop" class="fw-bold">
    {LABEL}
</label>
<span class="fw-bold text-danger">
    *
</span>

[StringLength(500)]
@Html.TextAreaFor(m => m.Prop)
<textarea id="Prop" name="Prop" cols="20" rows="2"> </textarea>
[StringLength(500)]
@Html.BootstrapTextAreaFor(m => m.Prop)
<textarea id="Prop" name="Prop" maxlength="500" class="form-control"></textarea>
<span class="textarea-remain">
    <span class="textarea-remain-value">
        500
    </span>
    characters remaining.
</span>

@Html.ValidationMessageFor(m => m.Prop)
<span class="field-validation-valid" data-valmsg-for="Prop" data-valmsg-replace="true"></span>
@Html.BootstrapValidationMessageFor(m => m.Prop)
<span class="field-validation-valid fw-bold text-danger small" data-valmsg-for="Prop" data-valmsg-replace="true"></span>

@Html.BootstrapToolTip("{TOOLTIP-TEXT}")
<span class="badge bg-primary" data-bs-toggle="tooltip" title="{tooltip-text}">
    ?
</span>