ASP.Net MVC 3 - CheckBoxList - Need some suggestio

2019-04-02 17:01发布

问题:

I am pretty new to ASP.Net MVC (and razor) and I have a few questions.

1)

I created an HTML extension to create a check box list as below:

public static HtmlString CheckBoxList(this HtmlHelper htmlHelper, string name, List<InputItemInfo> ItemInfo)
        {
            if (String.IsNullOrEmpty(name))
                throw new ArgumentException("The argument must have a value", "name");
            if (ItemInfo == null)
                throw new ArgumentNullException("ItemInfo");
            if (ItemInfo.Count < 1)
                throw new ArgumentException("The list must contain at least one value", "ItemInfo");

            StringBuilder sb = new StringBuilder();

            ItemInfo.Insert(0, new InputItemInfo("*", "Select All", ItemInfo.All(i => i.IsChecked)));

            foreach (InputItemInfo info in ItemInfo)
            {
                TagBuilder builder = new TagBuilder("input");
                if (info.IsChecked) builder.MergeAttribute("checked", "checked");
                builder.MergeAttribute("type", "checkbox");
                builder.MergeAttribute("value", info.Value);
                builder.MergeAttribute("name", name);
                builder.InnerHtml = info.DisplayText;
                sb.Append(builder.ToString(TagRenderMode.Normal));
                sb.Append("<br />");
            }

            return new HtmlString(sb.ToString());
        }

I was able to use this in my views and also get the values in the controller as shown below:

@model List<AppTest.Models.InputExtensionsViewModel>

@{
    ViewBag.Title = "Check";
}

<h2>Check</h2>

@using (Html.BeginForm())
{
    <table border="0" style="border:0px;">
        <tr>
            <td valign="top">
                @Html.Partial("CheckBoxList", Model[0])
            </td>

        </tr>
    </table>

    <br />

    <input type="submit" value="Go" />
}

<div style="font-weight:bolder">
    @ViewData["data"]
</div>

controller:

public ActionResult Check()
        {
            var model = new List<InputExtensionsViewModel>();

            var model1 = new InputExtensionsViewModel
            {
                Title = "Facilities",
                InputElementName = "facilities",
                InputElements = // a list
            };

            model.Add(model1);
            return View(model);
       }

    [HttpPost]
    public ActionResult Check(string[] facilities)
    {
               ...
    }

The model is:

public class InputExtensionsViewModel
    {
        public string Title { get; set; }
        public string InputElementName { get; set; }
        public List<InputItemInfo> InputElements { get; set; }

        public void SetSelected(string[] items)
        {
            if (items == null)
                return;

            this.InputElements.ForEach(delegate(InputItemInfo info)
            {
                if (items.Contains(info.Value))
                    info.IsChecked = true;
            });
        }
    }

My question is, is there a way by which I could bind the array items to a property in the InputExtensionsViewModel model? If I just add a property called facilities to the view model, it's not bound automatically and I can understand why, as I am not binding that in my view. But I cannot seem to think of a way by which I could do that.

This check box list is a user control and I just wanted to avoid having too many string[] array for my action methods.

[EDIT] - Okay, I was able to do this when I tried now. Not sure why it didn't work before.

2) And, I was checking for alternatives and found out this answer in SO:

CheckboxList in MVC3.0

And I was able to replicate this but my question is, how do i bind a label to this checkbox? My labels are dynamic and part of the model and so cannot be hard-coded. I was trying to use a Html.LabelFor but that didn't work. In the editor template, if I just @Model.Text, it won't work and will be lost after a post-back as its not bound to a property

I googled and found suggestions to create HTML helpers which is what I did earlier (my 1st question is about that).

Please let me know if something is unclear. I could elaborate. Any input is appreciated!

Thanks in advance!

回答1:

Ah, I found the solutions!

1) As indicated in my edit - adding a property with a similar name to a model and using it in the [HttpPost] enabled action method works fine. Guess last time I missed the getter and setters.

2) For this, in the editor template for MyViewModel, we just need to add this (** and **, needless to say, remove **!):

@model AppName.Models.MyViewModel
@Html.HiddenFor(x => x.Id)           
@Html.CheckBoxFor(x => x.IsChecked) **@Model.Text
@Html.HiddenFor(x => x.Text)**

EDIT:

I have changed this template to do more. Now there is a label control and it is associated to the checkboxes through jquery as shown below.

@model EncorPlusTest.Infrastructure.InputItemInfo

@Html.HiddenFor(model => model.Value)
@Html.CheckBoxFor(model => model.IsChecked) <label for="">@Model.Text</label>
@Html.HiddenFor(model => model.Text)
<br />

Then in jquery:

$('input:checkbox').each(function () {
        var lbl = $(this).next('input:hidden').next('label');
        var forID = $(this).attr('id');
        $(lbl).attr('for', forID);
    });

Hope its helpful for others!



回答2:

To answer part 2, you can easily add your label text as a property such as:

public class MyViewModel
{
    public int Id { get; set; }
    public bool IsChecked { get; set; }
    public string Text { get; set; }
}

Then your template would look similar to this:

@model AppName.Models.MyViewModel
@Html.HiddenFor(x => x.Id)           
@Html.CheckBoxFor(x => x.IsChecked)
@Html.LabelFor(x => x.Text)

The only downside to the above is that the label wouldn't be linked directly to the checkbox. You can accomplish this by doing something such as: CheckboxList in MVC3

Depending on the chance for re-usability, you can always create your own HtmlHelper as you were doing in the first part of this and wrap in the suggestions from the URL I pasted above.



回答3:

You don't jQuery to solve this problem. If you wrap the input with a label you get the same behavior.

By the way, another option, instead of a editor template, is a HTML Helper. Take a look at this:

public static class HtmlHelperExtensions
{


    #region CheckBoxList

    public static MvcHtmlString CheckBoxList(this HtmlHelper htmlHelper, string name, List<SelectListItem> listInfo)
    {
        return htmlHelper.CheckBoxList(name, listInfo, ((IDictionary<string, object>)null));
    }

    public static MvcHtmlString CheckBoxList(this HtmlHelper htmlHelper, string name, List<SelectListItem> listInfo, object htmlAttributes)
    {
        return htmlHelper.CheckBoxList(name, listInfo, ((IDictionary<string, object>)new RouteValueDictionary(htmlAttributes)));
    }

    public static MvcHtmlString CheckBoxList(this HtmlHelper htmlHelper, string name, List<SelectListItem> selectListItems, IDictionary<string, object> htmlAttributes)
    {
        // Verify arguments
        if (String.IsNullOrEmpty(name))
            throw new ArgumentNullException("name", "Name cannot be null");

        if (selectListItems == null)
            throw new ArgumentNullException("selectList", "Select list cannot be null");

        if (selectListItems.Count() < 1)
            throw new ArgumentException("Select list must contain at least one value", "selectList");

        // Define items
        StringBuilder items = new StringBuilder();

        int index = 0;
        // Loop through items)
        foreach (SelectListItem i in selectListItems)
        {
            // hidden value
            TagBuilder hiddenValue = new TagBuilder("input");
            hiddenValue.MergeAttribute("type", "hidden");
            hiddenValue.MergeAttribute("value", i.Value);
            hiddenValue.MergeAttribute("id", string.Format("{0}_{1}__Value", name, index));
            hiddenValue.MergeAttribute("name", string.Format("{0}[{1}].Value", name, index));
            // check box
            TagBuilder checkbox = new TagBuilder("input");
            if (i.Selected)
                checkbox.MergeAttribute("checked", "checked");
            checkbox.MergeAttribute("id", string.Format("{0}_{1}__Selected", name, index));
            checkbox.MergeAttribute("name", string.Format("{0}[{1}].Selected", name, index));
            checkbox.MergeAttribute("type", "checkbox");
            checkbox.MergeAttribute("value", "true");
            // wrapper label
            TagBuilder wrapperLabel = new TagBuilder("label");
            wrapperLabel.InnerHtml = checkbox.ToString(TagRenderMode.SelfClosing);
            wrapperLabel.InnerHtml += i.Text;
            // hidden selected
            TagBuilder hiddenSelected = new TagBuilder("input");
            hiddenSelected.MergeAttribute("type", "hidden");
            hiddenSelected.MergeAttribute("value", i.Selected.ToString().ToLower());
            hiddenSelected.MergeAttribute("name", string.Format("{0}[{1}].Selected", name, index));
            // label for checkbox
            TagBuilder checkBoxLabel = new TagBuilder("label");
            checkBoxLabel.MergeAttribute("for", checkbox.Attributes["id"]);
            checkBoxLabel.MergeAttribute("id", string.Format("{0}_{1}__Text", name, index));
            checkBoxLabel.MergeAttribute("name", string.Format("{0}[{1}].Text", name, index));
            // hidden text
            TagBuilder hiddenText = new TagBuilder("input");
            hiddenText.MergeAttribute("type", "hidden");
            hiddenText.MergeAttribute("value", i.Text);
            hiddenText.MergeAttribute("id", string.Format("{0}_{1}__Text", name, index));
            hiddenText.MergeAttribute("name", string.Format("{0}[{1}].Text", name, index));

            // Add item
            items.AppendLine(hiddenValue.ToString(TagRenderMode.SelfClosing));
            items.AppendLine(wrapperLabel.ToString(TagRenderMode.Normal));
            items.Append(hiddenSelected.ToString(TagRenderMode.SelfClosing));
            items.AppendLine(hiddenText.ToString(TagRenderMode.SelfClosing));

            items.AppendLine();

            index++;
        }

        return MvcHtmlString.Create(items.ToString());
    }
    public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
    {
        var name = ExpressionHelper.GetExpressionText(expression);
        var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
        return CheckBoxList(htmlHelper, name, metadata.Model as List<SelectListItem>);
    }





    #endregion

}