0

Can anyone tell me why, if my model clearly shows my values to be "true" or "false" MVC still thinks it says, "true,false." I assume this is because it is confusing the Request with my Model; however, I am explicitly telling it to use the Model.

enter image description here

As you can see in the image above, the model value is "true." However, in the image below, it thinks the value is "true,false." How can I make it just use "true?"

enter image description here

Model

public class TagCategorySearchViewModel
{
    public long Id { get; set; }

    public string Name { get; set; }

    public List<TagSearchViewModel> Tags { get; set; }
}

public class TagSearchViewModel
{
    public long Id { get; set; }

    public string Name { get; set; }

    public bool IsSelected { get; set; }
}

Controller

    [Authorize]
    public ActionResult FilterPrereqGrid(EditStudentPrerequisitesViewModel model, int page = 1)
    {
        model.Prerequisites = new List<PrerequisiteListViewModel>();
        var businessPartner = _bpManager.GetBusinessPartnerByMapGuid(model.BusinessPartnerMapGuid);

        model.Prerequisites.AddRange(_epManager.GetFullPrerequisitesLeftJoinedWithExperience(model.ExperienceId, businessPartner?.Id));

        // fix for how MVC binds checkboxes... it send "true,false" instead of just true, so we need to just get the true
        for (int i = 0; i < model.TagCategories?.Count(); i++)
        {
            for (int j = 0; j < model.TagCategories[i].Tags?.Count(); j++)
            {
                model.TagCategories[i].Tags[j].IsSelected = bool.Parse((Request.QueryString[$"TagCategories[{i}].Tags[{j}].IsSelected"] ?? "false").Split(',')[0]);
            }
        }

        var selectedTagIds = model.TagCategories?.SelectMany(x => x.Tags).Where(x => x.IsSelected == true).Select(x => x.Id).ToArray();

        // filter by selected tags
        if (selectedTagIds.Any())
        {
            model.Prerequisites = (from p in model.Prerequisites
                                   let prereqTagIds = p.Prerequisite.PrerequisiteTags.Select(et => et.TagId)
                                   where selectedTagIds.All(x => prereqTagIds.Contains(x))
                                   select p).ToList();
        }

        model.Prerequisites = (from m in model.Prerequisites
                               let ownerDocs = _deManager.GetDocumentsByOwnerAndSourceId(model.BusinessPartnerMapGuid, m.Prerequisite.Id).OrderByDescending(e => e.CreatedDate)
                               select new PrerequisiteListViewModel
                               {
                                   Prerequisite = m.Prerequisite,
                                   Selected = m.Selected,
                                   Mandatory = m.Mandatory,
                                   HasExpiration = m.Prerequisite.HasExpiration,
                                   BusinessExpirationPeriod = m.Prerequisite.ExpirationPeriod == 0 ? "None" : m.Prerequisite.ExpirationPeriod.ToString(),
                                   DocOwnerGuid = (ownerDocs.Any() ? model.BusinessPartnerMapGuid : Guid.Empty),
                                   DocRowGuid = (ownerDocs.Any() ? ownerDocs.First().DocRowguid : Guid.Empty),
                                   HasDocuments = ownerDocs.Any()
                               }).ToList();

        int rowsPerPage = 1;

        model.TotalRecords = model.Prerequisites.Count();
        model.Prerequisites = model.Prerequisites.Skip(page * rowsPerPage - rowsPerPage).Take(rowsPerPage).ToList();

        return PartialView("~/Views/BusinessExperience/_EditPrerequisites.cshtml", model);
    }
2
  • Please can you share code for the model and controller. Commented Mar 17, 2017 at 15:10
  • @buffjape Added. Thanks. Commented Mar 17, 2017 at 15:13

2 Answers 2

2

That is by design. The Html.CheckBoxFor extension actually renders something like the following:

<input type="checkbox" .. />
<input type="hidden" />

So both of these have the same name as the property you are rendering out. When checked, the checkbox returns "True", but when unchecked, the checkbox returns nothing. That is the way checkboxes work with form posts. In order to determine that nothing was checked, the MVC framework included a hidden field so that it would return "False" when not checked, but "True,False" when checked (since multiple items with the same name return this way from the form post). And then MVC model binding converts "True,False" to true.

You could just render your own <input type="checkbox" /> with a value of true, and that would just return true, but when unchecked, nothing gets rendered. Be aware of that...

Sign up to request clarification or add additional context in comments.

3 Comments

I considered that, but I am handling the way it sends, "true,false" in my action and am only ever returning the correct "true" or "false" value back to the view. I don't understand how it is still thinking that it says, "true,false" when it clearly does not as shown in the screenshot.
"true,false' is what comes back through the HTTP post. The default model binder for booleans changes true,false to true when assigned to a bool property.
Gotcha; I ended up needing to switch to POST anyway since GET has a limit (there is a limit to how much data you can shove into a querystring). So, all worked out using the default model binder. Thanks.
0

This actually works, it's a shame I can't just do the obvious way and have to write this all out though!

@model List<Prep2PracticeWeb.Models.ViewModels.TagCategorySearchViewModel>

@if (Model != null)
{
    <div class="tag-categories">
        @for (int i = 0; i < Model.Count(); i++)
        {
            @Html.HiddenFor(x => Model[i].Id)
            @Html.HiddenFor(x => Model[i].Name)
            <h4 data-toggle="collapse" data-target="#CollapsableTagCategory_@Model[i].Id" aria-expanded="false" aria-controls="CollapsableTagCategory_@Model[i].Id">
                <span class="glyphicon glyphicon-chevron-right"></span>
                <span class="glyphicon glyphicon-chevron-down"></span>
                @Model[i].Name
            </h4>
                            <div id="CollapsableTagCategory_@Model[i].Id" class="tag-container collapse">
                                @if (Model[i].Tags != null)
                                {
                                    for (int j = 0; j < Model[i].Tags.Count(); j++)
                                    {
                                        @Html.HiddenFor(x => Model[i].Tags[j].Id)
                                        @Html.HiddenFor(x => Model[i].Tags[j].Name)
                                        @* the following commented out line won't work because MVC is funny *@
                                        @*<label>@Html.CheckBoxFor(x => Model[i].Tags[j].IsSelected) @Model[i].Tags[j].Name</label>*@
                                        <label>
                                            <input @(Model[i].Tags[j].IsSelected ? @"checked=""checked""" : string.Empty) data-val="true" data-val-required="The IsSelected field is required." id="TagCategories_@(i)__Tags_@(j)__IsSelected" name="TagCategories[@i].Tags[@j].IsSelected" type="checkbox" value="true"> @Model[i].Tags[j].Name
                                            <input name="TagCategories[@i].Tags[@j].IsSelected" type="hidden" value="false">
                                        </label>
                                    }
                                }
                            </div>
        }
    </div>
}

Update: I realized GET was not an appropriate strategy in this scenario given the fact that the querystring can only be so long before the server returns a 500 error. I switched to POST which alleviated the issue.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.