我在MVC 3应用程序中遇到了同样的问题。我有一个创建新产品的视图,该产品可以分配给一个或多个类别。以下是我的EF Code第一模型类:
public class Product
{
public int ProductID { get; set; }
public string Name { get; set; }
public virtual ICollection<Category> Categories { get; set; }
}
public class Category
{
public int CategoryID { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
因此,我为创建产品视图创建一个视图模型,并包含产品和类别列表:
public class ProductEditViewModel
{
public Product Product { get; set; }
public List<SelectListItem> CategorySelections { get; set; }
public ProductEditViewModel(Product product, List<Category> categories)
{
this.Product = product;
CategorySelections = categories.Select(c => new SelectListItem()
{
Text = c.Name,
Value = c.CategoryID.ToString(),
Selected = (product != null ? product.Categories.Contains(c) : false)
}).ToList();
}
}
因此,我呈现了一个带有名称输入和每个类别(名为"Product.Categories")的复选框列表的视图。当我的表单返回时,我想保存产品及其关联的类别(或者,如果ModelState无效,则重新显示用户所做的完整的类别选择的视图)。
[HttpPost]
public ActionResult Create(Product product)
{
if (ModelState.IsValid)
{
db.Products.Add(product);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(new ProductEditViewModel(product, db.Categories.ToList()));
}
当我这样做并选择一个或多个类别时,ModelState无效,它返回带有以下验证错误的Edit视图:
值"25,2"无效。//25和2为categoryid
对我来说,它不能将25和2绑定到实际的类别对象中是有意义的,但是是否有一种标准的方法来使用自定义ModelBinder,允许我将id转换为类别并将它们附加到上下文?
你可以尝试如下:绑定到你的ViewModel而不是Product
在你的post动作:
[HttpPost]
public ActionResult Create(ProductEditViewModel viewModel)
{
if (ModelState.IsValid)
{
foreach (var value in viewModel.CategorySelections
.Where(c => c.Selected)
.Select(c => c.Value))
{
// Attach "stub" entity only with key to make EF aware that the
// category already exists in the DB to avoid creating a new category
var category = new Category { CategoryID = int.Parse(value) };
db.Categories.Attach(category);
viewModel.Product.Categories.Add(category);
}
db.Products.Add(viewModel.Product);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(new ProductEditViewModel(
viewModel.Product, db.Categories.ToList()));
}
我不确定这是否是"标准方式"。
编辑
当模型无效时,return
的情况不能在我上面的例子中工作,因为viewModel.Product.Categories
集合是空的,所以你将在视图中没有得到选择的类别项目,而不是用户之前选择的项目。
我不知道你是如何将集合绑定到视图(你的"复选框列表"?),但是当使用允许多次选择的ListBox
时,似乎有一个解决方案沿着这个答案的路线:在ListBoxFor中选择值的挑战。我刚才在评论中问Darin,所选项目id列表是否也会在post操作中绑定到ViewModel,他确认了。
谢谢@Slauma,这让我走上了正确的道路。下面是我的Create和Edit帖子方法,它们详细说明了如何管理关系(编辑有点棘手,因为它必须添加数据库中不存在的项,并删除已经删除但存在于数据库中的项)。我添加了一个SelectedCategories属性(int的列表)到我的ProductEditViewModel来保存表单的结果。
[HttpPost]
public ActionResult Create(ProductEditViewModel)
{
viewModel.Product.Categories = new List<Category>();
foreach (var id in viewModel.SelectedCategories)
{
var category = new Category { CategoryID = id };
db.Category.Attach(category);
viewModel.Product.Categories.Add(category);
}
if (ModelState.IsValid)
{
db.Products.Add(viewModel.Product);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(new ProductEditViewModel(viewModel.Product, GetCategories()));
}
对于Edit方法,我必须在数据库中查询当前产品,然后将其与viewModel进行比较。
[HttpPost]
public ActionResult Edit(ProductEditViewModel viewModel)
{
var product = db.Products.Find(viewModel.Product.ProductID);
if (ModelState.IsValid)
{
UpdateModel<Product>(product, "Product");
var keys = product.CategoryKeys; // Returns CategoryIDs
// Add categories not already in database
foreach (var id in viewModel.SelectedCategorys.Except(keys))
{
var category = new Category { CategoryID = id }; // Create a stub
db.Categorys.Attach(category);
product.Categories.Add(Category);
}
// Delete categories not in viewModel, but in database
foreach (var id in keys.Except(viewModel.SelectedCategories))
{
var category = product.Categories.Where(c => c.CategoryID == id).Single();
product.Categories.Remove(category);
}
db.SaveChanges();
return RedirectToAction("Index");
}
else
{
// Update viewModel categories so it keeps users selections
foreach (var id in viewModel.SelectedCategories)
{
var category = new Category { CategoryID = id }; // Create a stub
db.Categories.Attach(category);
viewModel.Product.Categories.Add(category);
}
}
return View(new ProductEditViewModel(viewModel.Product, GetCategories()));
}
这是更多的代码,我希望它会,但它实际上是相当有效的使用存根,只添加/删除已更改的
几天前我也遇到过类似的问题。最后使用了一个"hack"——MVC 3——绑定到一个带有List类型属性的复杂类型
如果您找到其他方法,请留言