【问题标题】:MVC DropDownListFor with ViewBag not passing value to controller带有 ViewBag 的 MVC DropDownListFor 未将值传递给控制器
【发布时间】:2015-05-28 02:01:39
【问题描述】:

我很难在我的创建视图中为相关模型/表设置下拉列表。 My Guest 模型通过以下方式引用 PersonPrefix 模型:

客座模特:

public virtual PersonPrefix Prefix { get; set; }

PersonPrefix 模型:

public class PersonPrefix
{

    [Key]
    public int PersonPrefixID { get; set; }
    [StringLength(6)]
    public string Abbreviation { get; set; }
    [StringLength(255)]
    public string Name { get; set; }
    public virtual ICollection<Guest> Guests { get; set; }

}

为了能够从数据库中获取数据并将其显示在下拉列表中,我做了以下操作:

控制器:

public ActionResult Create()
{
    ViewBag.PersonPrefixes = new SelectList(db.PersonPrefixes, "PersonPrefixID", "Abbreviation");
    return View();
} 

我已经在帖子中添加了前缀对象

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "GuestID,FirstName,MiddleName,Surname,BirthDate,SelectedPrefix")] Guest guest)
{
    if (ModelState.IsValid)
    {
        db.Guests.Add(guest);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(guest);
}

这是目前视图中的相关代码,但它没有将值传递给控制器​​:

  <div class="form-group">
        @Html.LabelFor(model => model.Prefix, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10" >
            @*@Html.DropDownListFor(m => m.Prefix,  new SelectList(ViewBag.PersonPrefixes, "Value", "Text", 1))*@
            @Html.DropDownListFor(m => m.Prefix,
            new SelectList(ViewBag.PersonPrefixes, "Value", "Text", 1))

        </div>
    </div>

感谢您的帮助!!

【问题讨论】:

  • 您不能将&lt;select&gt; 绑定到复杂对象(这是Prefix 的属性)。在您的情况下,您将需要使用具有属性(例如)int SelectedPrefix 的视图模型并绑定到该属性。
  • 谢谢!不过,我是 MVC 的新手,你能指出一个像样的例子吗?
  • 检查这个例子:dotnetfiddle.net/PtMRVw

标签: asp.net-mvc entity-framework viewbag dropdownlistfor


【解决方案1】:

您不能将&lt;select&gt; 元素绑定到复杂对象。所有 html 表单控件都回发一个值(或者在 &lt;select multiple&gt; 的情况下是一个值类型数组)。如果您选择了一个值为(例如)5 的选项,那么DefaultModelBinder 将尝试设置guest.Prefix = 5;,这当然会失败。

您需要绑定到值类型(例如intstring 等)。在您的情况下,您甚至无法绑定到PersonPrefixPersonPrefixID,因为验证将在PersonPrefix 的其他属性上失败。与往常一样,您应该使用仅包含您需要编辑的属性的视图模型。

public class GuestVM
{
  [Display(Name = "Prefix")]
  [Required(ErrorMessage = "Please select a prefix")]
  public int SelectedPrefix { get; set; }
  .... // other properties of Guest
  public SelectList PrefixList { get; set; }
}

控制器

public ActionResult Create()
{
  GuestVM model = new GuestVM();
  model.PrefixList = new SelectList(db.PersonPrefixes, "PersonPrefixID", "Abbreviation");
  .... // set other properties as required
  return View(model); // always return an instance of the model
}

查看

@Html.LabelFor(m => m.SelectedPrefix)
@Html.DropDownListFor(m => m.SelectedPrefix, Model.PrefixList, "--please select--")
@Html.ValidationMessageFor(m => m.SelectedPrefix)

然后在 POST 方法中,初始化Guest 数据模型的新实例,并从发布的视图模型映射其属性,最后保存数据模型。

public ActionResult Create(GuestVM model)
{
  if (!ModelSTate.IsValid)
  {
    model.PrefixList = new SelectList(db.PersonPrefixes, "PersonPrefixID", "Abbreviation");
    return View(model);
  }
  // Initialize a new instance of the data model and set its properties
  Guest guest = new Guest()
  {
    FirstName = model.FirstName,
    MiddleName = model.MiddleName,
    .... // other properties
    Prefix = db.PersonPrefixes.Find(model.SelectedPrefix)
  };
  db.Guests.Add(guest);
  db.SaveChanges();
  return RedirectToAction("Index");
}

旁注:您不需要在视图中创建另一个SelectList(它已经是SelectList)并且您尝试将所选值设置为1 的最后一个参数将被忽略(其值为你的绑定属性决定了选择哪个选项)所以如果你想用value="1"预先选择一个选项,那么在你将模型传递给视图之前,在控制器中设置SelectedPrefix = 1;的值。

【讨论】:

  • 感谢您的精彩解释!在我实施它之前只是一个问题:最佳实践是什么?我应该更改 Guest 模型以包含 public int PersonPrefixId(EF 仍然能够处理这样的代码优先迁移吗?)还是应该将复杂对象引用保留在模型中并构建视图模型?
  • 最佳实践是始终使用视图模型。有许多优点,例如将指定的显示和验证属性应用于属性、防止过度发布攻击、避免使用ViewBag 等(另请参阅What is ViewModel in MVC?automapper 等工具使数据之间的映射变得容易模型和视图模型。
  • 你摇滚。会试一试,让你知道它是怎么回事:)
  • 好的,我快到了。我只是不明白在 POST 方法中要做什么?它目前看起来像这样: [HttpPost] [ValidateAntiForgeryToken] public ActionResult Create([Bind(Include = "GuestID,FirstName,MiddleName,Surname,BirthDate,SelectedPrefix")] Guest guest) { if (ModelState.IsValid) { db.Guests .添加(客人); db.SaveChanges(); return RedirectToAction("索引"); } 返回视图(客人); }
  • 太难在 cmets 中阅读(建议您使用新代码编辑您的问题),但 POST 方法参数需要为 GuestVM(而不是 Guest)且 [Bind] 属性为不需要。给我 5 分钟,我将在 POST 方法中使用更多代码编辑我的答案,以使其更清晰。
【解决方案2】:

虽然最好使用 ViewModels,但根据斯蒂芬的回答,你在这里做错的具体是这样的:

首先,您的 DropDownListFor 是错误的。您的 ViewBag 条目已包含一个 SelectList,因此您无需创建另一个...您只需转换动态类型即可。

@Html.DropDownListFor(m => m.Prefix.PersonPrefixID, 
                           (SelectList)ViewBag.PersonPrefixes, "Select...")

第一个参数是从 PersonPrefixes 中的对象列表中选择的 id...所以当模型发布到控制器时,它将包含 ID(但不包含其他数据,因此您必须确保查询数据库以填充它)。

其次,您需要将 Post Action 更改为:

Create([Bind(Include = "GuestID,FirstName,MiddleName,Surname,BirthDate")] Guest guest)

然后在您的 PersonPrefix 类上使用 Bind 语法:

[Bind(Include = "PersonPrefixID")]
public class PersonPrefix
{
    [Key]
    public int PersonPrefixID { get; set; }
    .....
}

现在,当您发布时,PersonPrefix 的其余部分将为空,因为该数据不存在于帖子中,因此您必须使用发布的 PersonPrefixID 从数据库中检索它。

但是,正如我所说,最好使用 ViewModel。即便如此,我在这里所说的一些相同的事情仍然适用。您的 DropDownListFor 需要正确,并使用选定的 id 属性。而且,如果您使用的是 ViewModel,则没有理由使用 Bind 属性,因为您只是绑定了您需要的确切项目。

【讨论】:

  • 一个潜在的问题是PersonPrefix 的其他属性具有验证属性,因此ModelState 在回发时将无效-这意味着必须在测试@987654327 之前删除这些属性上的ModelState 错误@ 是有效的(因此如果应该返回视图进行更正)
  • @StephenMuecke - 只有StringLength,这是一个最大长度。如果你不绑定它,它不可能失败
猜你喜欢
  • 2012-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多