9

I am having a class as follows

 public class UserRoleModel
{
    public string Role { get; set; }
    public bool UserRole { get; set; }
}

and public UserRoleModel[] UserRoles { get; set; }


My controller is as follows:

 public ActionResult CreateUser()
     {
         UserDetailsModel model = new UserDetailsModel();
         return View(model);
     }

     [HttpPost]
     public ActionResult CreateUser(UserDetailsModel model)
     {

         return View(model);
     }

In my view I am having

    >@foreach (var item in Model.UserRoles)      
    { 

    name = "UserRoles"+ ".Value["+ i + "]"; 
    id= "UserRoles" + "_Value[" + i++ + "]";
    selected = item.UserRole ? "checked=\"checked\"" : ""; 

        <p>
        <input type="checkbox" name="@name" id="@id" @selected value="true" /> 
        <label for="@id">@item.Role</label> 
        <input type="hidden" name="@name" value="false" /> 
        </p> 
  } 

Despite the values being displayed accordingly in my view, there is no model bind back for UserRoles. What am I missing or is there any better and cleaner method?

learning
  • 11,415
  • 35
  • 87
  • 154

1 Answers1

22

Those kind of things are nicely achieved with editor templates. They also avoid you from writing spaghetti code in your views. Example:

Model:

public class UserDetailsModel
{
    public IEnumerable<UserRoleModel> Roles { get; set; }
}

public class UserRoleModel
{
    public string Role { get; set; }
    public bool UserRole { get; set; }
}

Controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View(new UserDetailsModel
        {
            // Fill with some dummy stuff
            Roles = Enumerable.Range(1, 5).Select(x => new UserRoleModel
            {
                Role = "role " + x,
                UserRole = false
            })
        });
    }

    [HttpPost]
    public ActionResult Index(UserDetailsModel model)
    {
        return View(model);
    }
}

View (~/Views/Home/Index.cshtml):

@model AppName.Models.UserDetailsModel
@using (Html.BeginForm())
{ 
    @Html.EditorFor(x => x.Roles)
    <input type="submit" value="OK" />
}

Editor template (~/Views/Home/EditorTemplates/UserRoleModel.cshtml):

@model AppName.Models.UserRoleModel
@Html.CheckBoxFor(x => x.UserRole)
@Html.LabelFor(x => x.Role, Model.Role)
@Html.HiddenFor(x => x.Role)

Now that's what I call clean stuff.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • One thing I didn't notice until I actually tried it - the @Html.EditorFor(x=>x.Roles) will actually iterate through the collection. Clean indeed. – chris Mar 13 '12 at 16:51
  • Great. This looks like a nice solution for what I am looking for (finally). Will definately try it out tomorrow. Couldn't find something I was content with that also bound back on a HttpPost; probably was searching for the wrong thing. Most solutions needed an extra array or other paramter in the HttpPost Action of the Controller. Thank you Darin. – Rodi Mar 06 '13 at 19:35
  • 1
    Tested it now and it works for my situation too. But The I changed the `LabelFor` to use the same property as the `CheckBoxFor` so that clicking the label will trigger the checkbox too. In this example it would be: `@Html.LabelFor(x => x.UserRole, Model.Role)` – Rodi Mar 07 '13 at 06:49
  • Can this be done one level deeper? For instance one page with a list of users with a list of checkboxes for their role? (not actual case but similar architecture – Dylan Snel May 31 '13 at 11:17