When I'm trying to get values in Post the values of checkboxes are set to NULL when I don't check then in order (1, 2, 3, etc).
I need to select any of them in no order (i.e. 4, 5).
MODEL:
public class AssignUsersViewModel
{
[Display(Name = "Check to select")]
public bool Select { get; set; }
public int Id { get; set; }
[Display(Name = "App. Username")]
public string UserName { get; set; }
[Required]
public string GivenName { get; set; }
[Required]
public string Surname { get; set; }
[Display(Name = "Roles")]
public IList<Roles> Roles { get; set; }
}
public class AssignUsersAddModel
{
public bool Select { get; set; }
public int Id { get; set; }
public IEnumerable<SelectedRoles> selectedRoles { get; set; }
}
public class SelectedRoles
{
public string Name { get; set; }
}
CSHTML:
@model IList<AspNetIdentity2DRH.Models.AssignUsersViewModel>
@using (Html.BeginForm("UsersAddToApp", "UsersAdmin", FormMethod.Post))
{
@Html.AntiForgeryToken()
<table class="table">
<tr>
<th>Check for add</th>
<th>Username</th>
<th>Givenname</th>
<th>Surename</th>
<th>Roles</th>
</tr>
@for (int i = 0; i < Model.Count(); i++)
{
<tr>
<td>
@Html.CheckBoxFor(x => x[i].Select)
@Html.HiddenFor(x => x[i].Id)
</td>
<td>
@Html.DisplayFor(x => x[i].UserName)
</td>
<td>
@Html.DisplayFor(x => x[i].GivenName)
</td>
<td>
@Html.DisplayFor(x => x[i].Surname)
</td>
<td>
<div class="row">
<div class="form-group">
@for (int j = 0; j < Model[i].Roles.Count(); j++)
{
<div class="col-sm-4">
<input type="checkbox" name="[@i.ToString()].selectedRoles[@j.ToString()].Name" value="@Model[i].Roles[j].Name" class="checkbox-inline" />
@Html.Label(Model[i].Roles[j].Name, new { @class = "control-label", @data_toggle = "tooltip", @data_placement = "top", @data_original_title = Model[i].Roles[j].Description })
</div>
}
</div>
</div>
</td>
</tr>
}
</table>
<p>
<input type="submit" value="Add existing user" class="btn btn-primary" />
<input type="button" value="Cancel" onclick="window.location.href = '@Url.Action("UsersIndex", "UsersAdmin")';" class="btn btn-cancel" />
</p>
}
CONTROLLER:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult UsersAddToApp(List<AssignUsersAddModel> model)
{
if (ModelState.IsValid)
{
foreach (AssignUsersAddModel item in model)
{
if (item.Select)
{
using (DbContextTransaction dbtrans = db.Database.BeginTransaction())
{
try
{
int appId = (int)Session["ApplicationId"];
Users user = UserManager.FindById(item.Id);
db.ApplicationUsers.Add(new ApplicationUsers { ApplicationId = appId, UserId = user.Id });
db.SaveChanges();
foreach (SelectedRoles RolesItem in item.selectedRoles)
{
int roleId = db.Roles.Where(r => r.Name == RolesItem.Name).Select(r => r.Id).FirstOrDefault();
db.UserApplicationRoles.Add(new UserApplicationRoles { ApplicationId = appId, UserId = user.Id, RoleId = roleId });
db.SaveChanges();
}
dbtrans.Commit();
}
catch (Exception)
{
dbtrans.Rollback();
}
}
}
}
return RedirectToAction("UsersAddToApp");
}
ModelState.AddModelError("", "An error has occurred. Contact system administrator.");
return RedirectToAction("UsersAddToApp");
}
The problem is when I select checkboxes (all except the first, or the last o one in the middle, the line:
foreach (SelectedRoles RolesItem in item.selectedRoles)
Sends item.selectedRoles is null.
How I could do this right?
Nice accepted answer - this is an additional note regarding checkbox and binding.
If you look at the output for a
CheckBoxFor
, you'll see that there are two inputs for field, eg:gives
this is because an unticked checkbox does not get included in the post, so the second hidden field provides the false (unticked) value to the controller.
When you create the
input
manually, you can also add thishidden field
and it will then pass back with false values and the modelbinder should then pick it up as you were originally expecting:In the case of the question, I would certainly refactor and use Html helpers as per the accepted answer.
In other cases, the checkboxes may be added dynamically via javascript, so the above hidden input field is the way to go.
Well I found that IEnumerable don't work wthout order (great news)
Son I change the name of the checkbox to:
Then in the controller, For every single enumerable item I can get the following
Which returns (in comma-separated) a string with the name of selected items.
So far I have no other idea, but it works for me... I only need to restrict commas in the name attribute.
The
DefaultModelBinder
will only bind collections where the indexers of the collection items start at zero and are consecutive. You problem is that you are manually creating a checkbox element. Since unchecked checkboxes do not post back, if you uncheck one, then it and any subsequent checkbox values will not be bound to the collection when you submit.Next your trying to bind a checkbox to a
string
value. A checkbox has 2 states and is designed to represent a boolean value.You have not shown you
Role
(view) model but it should include aboolean
property indicating if it has been selectedThen in the view, use strongly type html helpers so you get correct 2 way model binding
Then in the POST method, your collection will be correctly bound and you could access the selected roles using say,
Note you may also want to include a hidden input for the
Role.ID
(rather that theRole.Name
property) so you do not need to perform database lookups in your POST methodsforeach
loop.Side note: Your post method needs to be
not
ListAssignUsersAddModel> model