POST to MVC IEnumerable controller nested model is null when I don't select checkboxes ok

When I try to get the values โ€‹โ€‹in the column, the checkbox values โ€‹โ€‹are set to NULL unless I check them in order (1, 2, 3, etc.).

I need to select any of them in 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 the checkboxes (everything but the first or last o in the middle, the line:

foreach (SelectedRoles RolesItem in item.selectedRoles)

      

Sends the item .selectedRoles is null.

How could I get it right?

+3


source to share


3 answers


DefaultModelBinder

will only bind collections in which collection element indexes are zero-based and sequential. The problem is that you are manually creating the checkbox element. Since unchecked checkboxes are not returned, if you uncheck the checkbox, then it and any subsequent checkbox values โ€‹โ€‹will not be bound to the collection on submission.

Then you will try to bind the checkbox to the value string

. The checkbox has 2 states and is intended to represent a boolean value.

You didn't show you the Role

view, but it should include a property boolean

to indicate if it was selected

public class Role
{
  public int ID { get; set; }
  public string Name { get; set; }
  public bool IsSelected { get; set; }
}

      

Then in the view use the syntax html helpers so that you get the correct 2-side model binding

@for (int j = 0; j < Model[i].Roles.Count; j++)
{ 
  @Html.HiddenFor(m => m[i].Roles[j].Name) // or better use the ID property
  @Html.CheckBoxFor(m => m[i].Roles[j].IsSelected)
  @Html.LabelFor(m => m[i].Roles[j].IsSelected, Model[i].Roles[j].Name)
}

      



Then, in the POST method, your collection will be bound correctly and you can access the selected roles using, say,

List<string> selectedRoles = model.Roles.Where(r => r.IsSelected);

      

Note that you can also enable hidden input for Role.ID

(rather, this is a property Role.Name

), so you don't need to search the database in POST methods foreach

.

Side note: your post method should be

public ActionResult UsersAddToApp(List<AssignUsersViewModel> model)

      

not ListAssignUsersAddModel> model

+5


source


The good accepted answer is an additional note on checkbox and binding.

If you look at the output for CheckBoxFor

, you can see that there are two inputs for the field, for example:

@Html.CheckBoxFor(x=>x.Flag)

      

gives

<input type='checkbox' name='Flag' ..              // value etc based existing value
<input type='hidden' name='Flag' value='false' />  // always false

      



this is because the untagged checkbox is not included in the message, so the second hidden field provides a false (unticked) value for the controller.

When you create input

manually, you can also add this one hidden field

, after which it will return with false values, and the model hook should pick it up as you originally expected:

<input type="checkbox" name="[@i.ToString()].selectedRoles[@j.ToString()].Name" value="@Model[i].Roles[j].Name" class="checkbox-inline" />
<input type="hidden" name="[@i.ToString()].selectedRoles[@j.ToString()].Name" value="@Model[i].Roles[j].Name" value='false' />

      

In case of a question, I would definitely refactor and use Html helpers as per the accepted answer.

In other cases, checkboxes can be added dynamically via javascript, so the specified hidden input field is the way to go.

+1


source


Well I found IEnumerable not working with order (great news)

Son I change the name of the checkbox to:

  • name = "[@ i.ToString ()]. โ€‹โ€‹SelectedRoles.Name"

Then in the controller, for each individual element, the following can be obtained

  • HttpContext.Request.Params ["[1] .selectedRoles.Name"]

Returns (comma separated) a string with the name of the selected items.

I have no other idea yet, but it works for me ... I need to delimit the commas in the name attribute.

0


source







All Articles