在POST上将模型绑定到单选按钮

时间:2022-09-26 19:18:09

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?

我正在尝试扩展使用EF6和MVC生成的用户注册表单,我希望能够显示显示为单选按钮的用户角色列表,我相信我已经完成了。但是我遇到的麻烦是当我将表单发布回控制器时,它没有将选定的选项绑定回视图模型。我究竟做错了什么?

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?

但是,当提交表单时,模型永远不会有效,因为角色永远不会被绑定。 (或者至少那是我的信念)任何想法?

3 个解决方案

#1


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.

然后,这会将所选值回发给UserRole属性。

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.

注意:我认为你还需要使用radiobutton的值来让它填充回UserRole,因为我认为item.Name不起作用。您可能希望使用名称回发到字符串字段,然后在控制器的post方法中查找正确的角色。

#2


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.

这是我从堆栈溢出的参考。

#3


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.

Html控件(输入,textarea,select)回发他们的名称/值对。在您的情况下,与单选按钮关联的表单数据将是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.

假设您的POST方法是公共ActionResult寄存器(RegisterViewModel模型),DefaultModelBinder首先初始化RegisterViewModel的实例,然后找到匹配的表单数据名称/值对,并尝试将属性UserRoles的值设置为字符串“someValue”,这当然会失败因为UserRoles是一个复杂的对象,UserRoles变为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.

您不能将html控件绑定到复杂对象,除非您创建自定义ModelBinder和/或ValueProviders,即使这样,它也只会设置您模型的一个属性,因为您只回发一个值。

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)

例如,您需要创建一个视图模型,其中的属性表示您要在视图中显示/编辑的内容(假设typeof IdentityRole具有属性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.

当您回发时,您可以从模型SelectedRole属性中获取所选角色的ID。

#1


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.

然后,这会将所选值回发给UserRole属性。

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.

注意:我认为你还需要使用radiobutton的值来让它填充回UserRole,因为我认为item.Name不起作用。您可能希望使用名称回发到字符串字段,然后在控制器的post方法中查找正确的角色。

#2


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.

这是我从堆栈溢出的参考。

#3


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.

Html控件(输入,textarea,select)回发他们的名称/值对。在您的情况下,与单选按钮关联的表单数据将是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.

假设您的POST方法是公共ActionResult寄存器(RegisterViewModel模型),DefaultModelBinder首先初始化RegisterViewModel的实例,然后找到匹配的表单数据名称/值对,并尝试将属性UserRoles的值设置为字符串“someValue”,这当然会失败因为UserRoles是一个复杂的对象,UserRoles变为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.

您不能将html控件绑定到复杂对象,除非您创建自定义ModelBinder和/或ValueProviders,即使这样,它也只会设置您模型的一个属性,因为您只回发一个值。

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)

例如,您需要创建一个视图模型,其中的属性表示您要在视图中显示/编辑的内容(假设typeof IdentityRole具有属性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.

当您回发时,您可以从模型SelectedRole属性中获取所选角色的ID。