Code Copied

在MVC中运用Ace Admin主题的Checkbox

1. 在Ace Admin主题中使用CheckBox

在使用ASP.NET MVC中使用Ace Admin主题时,系统默认的CheckBox样式并不是很优雅

image

若想采用Ace Admin主题中较为漂亮的CheckBox样式

image

则定义这样的代码:

<input type="checkbox" name="mycheckbox" class="ace">
<span class="lbl"></span>

值得注意的是,第2行的span代码是必须的,否则是无法看到复选框的

2. 在MVC中使用CheckBox

在MVC中使用CheckBox时,常常会采用HtmlhHelper扩展中的CheckBoxFor方法,通过这个方法可以绑定和验证实体对象的bool类型属性。

<div class="form-group">
    @Html.Label("是否激活", new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.CheckBoxFor(model => model.IsActivated)
        @Html.ValidationMessageFor(model => model.IsActivated)
    </div>
</div>

在运行时MVC实际输出的html代码则是由html控件的checkbox, hidden和span组成。

关于MVC是如何处理CheckBox控件的,在StackOverflow上有一段很精辟的解释

If checkbox is not selected, form field is not submitted. That is why there is always false value in hidden field.
If you leave checkbox unchecked, form will still have value from hidden field. That is how ASP.NET MVC handles checkbox values.

<div class="col-md-10">
    <input class="check-box" data-val="true" data-val-required="The IsActivated field is required." id="IsActivated" name="IsActivated" type="checkbox" value="true">
    <input name="IsActivated" type="hidden" value="false">
    <span class="field-validation-valid" data-valmsg-for="IsActivated" data-valmsg-replace="true"></span>
</div>

通过生成的代码,可以清晰地知道直接在CheckBoxFor中使用Ace Admin的样式是不行的。

Hide Code | Copy Code

@Html.CheckBoxFor(model => model.IsActivated,new { @class = "ace" })

因为checkbox后面跟了一个hidden控件,而不是一个<span class=”lbl”></span>。

3. 自定义CheckBoxFor

问题已经抛出来了——MVC自带的CheckBoxFor不能直接采用Ace Admin主题的样式。

当然也许我们会有一个直接的解决办法:不采用CheckBoxFor控件了,直接使用纯纯的html代码,在Action中用FormCollection来接收checkbox的值。
这确实是一个好办法,但我不赞同这种做法,原因有2点:

  • Action中将会存在大量的FormConllecton[…]代码
  • FormCollection中非String类型的键值,需要进行强制类型转换后才能将值赋给实体

咱就想使用和CheckBoxFor一样的功能,直接能够绑定到实体属性,而且还能应用Ace Admin主题的效果。

那就只有扩展HtmlHelper,自定义CheckBoxFor了。
如何实现?在MVC输出checkbox代码时后面直接跟出<span class=”lbl”></span>代码。
下面我们将实现AceCheckBoxFor控件。

在做AceCheckBoxFor之前,我参考了MVC中的CheckBoxFor源码,下面就是AceCheckBoxFor的源码了:

public static class AceCheckBoxHelper
    {
        public static MvcHtmlString AceCheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, bool>> expression)
        {
            return AceCheckBoxFor(htmlHelper, expression, htmlAttributes: null);
        }

        public static MvcHtmlString AceCheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, bool>> expression, object htmlAttributes)
        {
            return AceCheckBoxFor(htmlHelper, expression, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
        }

        public static MvcHtmlString AceCheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, bool>> expression, IDictionary<string, object> 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",
                isChecked: isChecked ?? false,
                setId: true,
                htmlAttributes: attributes);
        }

        private static MvcHtmlString InputHelper(HtmlHelper htmlHelper, InputType inputType, ModelMetadata metadata,
            string name, object value, bool isChecked, bool setId, IDictionary<string, object> htmlAttributes)
        {
            string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);

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

            string valueParameter = htmlHelper.FormatValue(value, null);


            if (isChecked)
            {
                tagBuilder.MergeAttribute("checked", "checked");
            }
            tagBuilder.MergeAttribute("value", valueParameter);

            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));
            // 应用Ace Admin主题样式
            tagBuilder.AddCssClass("ace");

            // Render an additional <input type="hidden".../> for checkboxes. This
            // addresses scenarios where unchecked checkboxes are not sent in the request.
            // Sending a hidden input makes it possible to know that the checkbox was present
            // on the page when the request was submitted.
            StringBuilder inputItemBuilder = new StringBuilder();
            inputItemBuilder.Append(tagBuilder.ToString(TagRenderMode.SelfClosing));

            // 在checkbox后直接加入span,让复选框可见
            inputItemBuilder.Append("<span class=\"lbl\"></span>");

            TagBuilder hiddenInput = new TagBuilder("input");
            hiddenInput.MergeAttribute("type", HtmlHelper.GetInputTypeString(InputType.Hidden));
            hiddenInput.MergeAttribute("name", fullName);
            hiddenInput.MergeAttribute("value", "false");
            inputItemBuilder.Append(hiddenInput.ToString(TagRenderMode.SelfClosing));
            return MvcHtmlString.Create(inputItemBuilder.ToString());
        }

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

使用方式与CheckBoxFor是没有什么差别的。

<div class="form-group">
    @Html.Label("是否激活", new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.AceCheckBoxFor(model => model.IsActivated)
    </div>
</div>