0

UPDATE: The solution was to use an EditorTemplate. See solution below:

I want to pass a model to/from a controller which let's me set name, and set the value on an undetermined roles (as checkboxes). When I examine the postback, I get a value for Name in model, but Roles is null. How can I tell which checkboxes were checked?

Model:

public class MyModel
{
    public string Name { get; set; }
    public IEnumerable<RoleItem> Roles { get; set; }
}

public class RoleItem
{
    public String Name { get; set; }
    public String Id { get; set; }
    public bool Selected { get; set; }
    public RoleItem(String id, String name, bool selected = false)
    {
        this.Name = name;
        this.Id = id;
        this.Selected = selected;
    }
}

Razor:

@model WebApplication1.Controllers.MyModel

@using (Html.BeginForm("Index", "Home", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary()

    @Html.TextBoxFor(m=>m.Name)

    foreach (var m in Model.Roles)
    {
        <div>
            @Html.Label(m.Id, m.Name)
            @Html.CheckBox(m.Id, m.Selected, new { id = @m.Id })
        </div>
    }
    <input type="submit"/>
}
WhiskerBiscuit
  • 4,795
  • 8
  • 62
  • 100
  • you should have a messaging service between your Controller->Model->Viewer<-Controller – Mike Jan 30 '14 at 21:10

1 Answers1

0

GOAL: To allow any Administrator to add new users to the Asp identity tables and assign them roles that are defined in a list using checkboxes.

Model:

public class RegisterViewModel
{
    [Display(Name = "Name")]
    public string FullName { get; set; }

    [Required]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm password")]
    [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }

    public List<RoleItem> Roles { get; set; }

}

public class RoleItem
{
    public String Name { get; set; }
    public String Id { get; set; }
    public bool IsMember { get; set; }
}

Controller (GET): This reads all of the roles in the database and transforms them to a list of RoleItems. This will prepend the character "r" onto the id field as some browsers have a problem with an id starting with a number. We want to make sure the "Users" group is checked by default, so we find this value in the list and set the IsMember property to true. We check the request to see if the page was redirected here from a successful POST (see below)

    // GET: /Account/AddUser
    [Authorize(Roles = "Administrators")]
    public ActionResult AddUser()
    {
        var rolesDb = new ApplicationDbContext();  //Users, Administrators, Developers, etc
        ViewBag.AddSuccess = Request["added"]=="1" ? true : false;

        var roleItems = rolesDb.Roles.Select(r => new RoleItem() { Id = "r" + r.Id, Name = r.Name, IsMember = false }).ToList(); //add an r to get around a browser bug
        var users = roleItems.FirstOrDefault(r => r.Name == "Users");  //Get the row that has the Users value and set IsMember=true
        if (users != null) 
            users.IsMember = true;
        var m = new RegisterViewModel() {Roles = roleItems};
        return View(m);
    }

View: Pretty standard stuff. Note @Html.EditorFor(x => x.Roles) at the bottom, which uses an editor template (follows)

@model cherry.Models.RegisterViewModel
@{
    ViewBag.Title = "AddUser";
}

<h2>@ViewBag.Title.</h2>

@if (Convert.ToBoolean(ViewBag.AddSuccess))
{
    <text>User added!</text>
}

@using (Html.BeginForm("AddUser", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
    @Html.AntiForgeryToken()
    <h4>Create a new account.</h4>
    <hr />
    @Html.ValidationSummary()
    <div class="form-group">
        @Html.LabelFor(m => m.EmailAddress, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.EmailAddress, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.FullName, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.FullName, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.Password, new { value = Model.Password, @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.ConfirmPassword, new {@class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.ConfirmPassword, new {value = Model.ConfirmPassword,  @class = "form-control" }) 
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" class="btn btn-default" value="Register" />
        </div>
    </div>

    @Html.EditorFor(x => x.Roles)

}

EditorTemplate:

You MUST give the template the same name as the object you are creating the template for. You must also put this object in a folder called EditorTemplates below the view you are designing this for, or you can put the folder inside the shared folder.

Views\Account\EditorTemplates\RoleItem.cshtml

@model cherry.Models.RoleItem
<div>
    @Html.CheckBoxFor(x => x.IsMember)
    @Html.LabelFor(x => x.IsMember, Model.Name)
    @Html.HiddenFor(x => x.Name)
</div>
WhiskerBiscuit
  • 4,795
  • 8
  • 62
  • 100