简体   繁体   中英

Bind Model to Radio Buttons on POST

I am trying to expand on the user register form that is generated using EF6 and MVC, I want to be able to display a list of user roles that is displayed as radio buttons, which I believe I have done. However the trouble I am having is when I post the form back to the controller, it's not binding the selected option back to the view model. What am I doing wrong?

My View Model:

public class RegisterViewModel
{
    [Required]
    [Display(ResourceType = typeof(ResourceStrings), Name = "Username")]
    [StringLength(50, ErrorMessageResourceType = typeof(ResourceStrings), ErrorMessageResourceName = "MinLengthValidation", MinimumLength = 4)]
    public string Username { get; set; }

    [Required]
    [Display(ResourceType = typeof(ResourceStrings), Name = "FirstName")]
    [StringLength(50, ErrorMessageResourceType = typeof(ResourceStrings), ErrorMessageResourceName = "MinLengthValidation", MinimumLength = 2)]
    public string FirstName { get; set; }

    [Required]
    [Display(ResourceType = typeof(ResourceStrings), Name = "Surname")]
    [StringLength(50, ErrorMessageResourceType = typeof(ResourceStrings), ErrorMessageResourceName = "MinLengthValidation", MinimumLength = 2)]
    public string Surname { get; set; }

    //Other fields removed to save space

    [Required]
    [Display(Name = "Roles")]
    public IEnumerable<IdentityRole> UserRoles { get; set; }
}

I have a list of Identity Roles that I want to pass to the view like so:

// GET: /Account/Register
    [AllowAnonymous]
    public ActionResult Register()
    {
        var model = new RegisterViewModel();
        model.UserRoles =  new RoleManager<IdentityRole>(new RoleStore<IdentityRole>()).Roles.ToList();

        return View(model);
    }

And on the view I display the roles like so:

     <div class="panel-body">
            @foreach (var item in Model.UserRoles)
            {
                <div class="col-md-8">
                    <div>
                        @Html.RadioButtonFor(m => m.UserRoles, item.Name)
                        @Html.DisplayFor(m => item.Name, new { @class = "col-md-3 control-label" })
                    </div>
                </div>
            }
            @Html.ValidationMessageFor(m => m.UserRoles, "", new { @class = "text-danger" })
        </div>

However when the form is submitted back the model is never valid as the role is never bound. (Or at least that's what I believe) Any ideas?

You do not want to have the radio buttons tied to the list of values, you need to change you model to a list of roles and the selected role like this

//Other fields removed to save space

[Required]
[Display(Name = "Role")]
public IdentityRole UserRole { get; set; }

[Display(Name = "Roles")]
public IEnumerable<IdentityRole> UserRoles { get; set; }

Then you create the radio button like this

@Html.RadioButtonFor(m => m.UserRole, item.Name)

This will then post back the selected value to the UserRole property.

NOTE: I think you will also need to play with the value of the radiobutton to get it to populate back to the UserRole since I do not think item.Name would work. You may want to post back to a string field with the name and then look up the proper role in the post method of the controller.

I looked into this (Even tested the theory since I had one available) and it is impossible. You can bind any sub property of the selected object to your view model but not the object in its entirety.

Here is my reference from stack overflow.

Html controls (input, textarea, select) post back their name/value pairs. In your case the form data associated with the radio button would be UserRoles: someValue .

Assuming your POST method is public ActionResult Register(RegisterViewModel model) , the DefaultModelBinder first initializes an instance of RegisterViewModel then finds the matching form data name/value pair and tries to set the value of property UserRoles to the string "someValue" which of course fails because UserRoles is a complex object and UserRoles becomes null .

You cannot bind html controls to complex objects unless you create a custom ModelBinder and/or ValueProviders and even then it will only set one property of you model because you are only posting back one value.

You need to create a view model with properties representing what you want to display/edit in the view, for example (assuming typeof IdentityRole has a property int ID )

public class RegisterViewModel
{
  [Required]
  [Display(ResourceType = typeof(ResourceStrings), Name = "Username")]
  [StringLength(50, ErrorMessageResourceType = typeof(ResourceStrings), ErrorMessageResourceName = "MinLengthValidation", MinimumLength = 4)]
  public string Username { get; set; }
  ....

  // For binding the selected roles
  [Required(ErrorMessage = "Please select a role")]
  public int ID SelectedRole { set; set; }

  // For displaying the available roles 
  [Display(Name = "Roles")]
  public IEnumerable<IdentityRole> UserRoles { get; set; }
}

Then in the view

@model yourAssembly.RegisterViewModel
@using (Html.BeginForm())
{
  ....
  foreach(var role in Model.UserRoles)
  {
    @Html.RadioButtonFor(m => m.SelectedRole, role.ID, new { id = role.Name })
    <label for="@role.Name">@role.Name</label>
  }
  @Html.ValidationMessageFor(m => m.SelectedRole)
  ....
}

and when you post back, you can get the ID of the selected role from the models SelectedRole property.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM