Reputation: 21430
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.
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?"
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
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
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