user1477388
user1477388

Reputation: 21430

MVC Not Parsing the Correct Value from Model

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);
    }

Upvotes: 0

Views: 62

Answers (2)

user1477388
user1477388

Reputation: 21430

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.

Upvotes: 0

Brian Mains
Brian Mains

Reputation: 50728

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...

Upvotes: 2

Related Questions