我有一个简单的模型,它使用多选列表框来实现许多EF关系。
在我的Create
操作中,我收到了错误
从“System.String”类型到“MyProject.Models.Location”类型的参数转换失败,因为没有类型转换器可以在这些类型之间进行转换。
我有两个模型,一篇文章和一个位置:
Article.cs
namespace MyProject.Models
{
public class Article
{
public Article()
{
Locations = new List<Location>();
}
[Key]
public int ArticleID { get; set; }
[Required(ErrorMessage = "Article Title is required.")]
[MaxLength(200, ErrorMessage = "Article Title cannot be longer than 200 characters.")]
public string Title { get; set; }
public virtual ICollection<Location> Locations { get; set; }
}
Location.cs:
namespace MyProject.Models
{
public class Location
{
[Key]
public int LocationID { get; set; }
[Required(ErrorMessage = "Location Name is required.")]
[MaxLength(100, ErrorMessage = "Location Name cannot be longer than 100 characters.")]
public string Name { get; set; }
public virtual ICollection<Article> Articles { get; set; }
}
}
我有一个ViewModel:
namespace MyProject.ViewModels
{
public class ArticleFormViewModel
{
public Article article { get; set; }
public virtual List<Location> Locations { get; set; }
public ArticleFormViewModel(Article _article, List<Location> _locations)
{
article = _article;
Locations = _locations;
}
}
}
create.cshtml:
@model MyProject.ViewModels.ArticleFormViewModel
<h2>Create</h2>
@using (Html.BeginForm()) {
@Html.AntiForgeryToken()
@Html.ValidationSummary(true)
<fieldset>
<legend>Article</legend>
<div class="editor-label">
@Html.LabelFor(model => model.article.Title)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.article.Title)
@Html.ValidationMessageFor(model => model.article.Title)
</div>
<h3>Locations</h3>
@Html.ListBoxFor(m=>m.article.Locations,new MultiSelectList(Model.Locations,"LocationID","Name"))
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
最后我的控制器动作:
// GET: /Article/Create
public ActionResult Create()
{
var article = new Article();
var AllLocations = from l in db.Locations
select l;
ArticleFormViewModel viewModel = new ArticleFormViewModel(article, AllLocations.ToList());
return View(viewModel);
}
//
// POST: /Article/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Article article)
{
var errors = ModelState.Values.SelectMany(v => v.Errors);
if (ModelState.IsValid)
{
var locations = Request.Form["article.Locations"];
if (locations != null)
{
var locationIDs = locations.Split(',');
foreach (var locationID in locationIDs)
{
int id = int.Parse(locationID);
Location location = db.Locations.Where(l => l.LocationID == id).First();
article.Locations.Add(location);
}
}
db.Articles.Add(article);
db.SaveChanges();
return RedirectToAction("Index");
}
var AllLocations = from l in db.Locations
select l;
ArticleFormViewModel viewModel = new ArticleFormViewModel(article, AllLocations.ToList());
return View(viewModel);
}
这一切都运行得很好,我的位置列表框已正确填充:
如果我没有选择位置,那么我的模型会正确保存。如果我选择一个或多个位置,那么我的Model.IsValid检查将失败,并显示异常
从“System.String”类型到“MyProject.Models.Location”类型的参数转换失败,因为没有类型转换器可以在这些类型之间进行转换。
但是,如果我删除了ModelState.IsValid检查,那么即使出现错误,我的值也会正确保存到数据库中 - 只是我失去了对模型标题等内容的验证。
希望有人可以提供帮助!
答案 0 :(得分:1)
除非您创建类型转换器,否则无法直接将列表框的结果绑定到这样的复杂对象。原因在于MVC只能处理发布的HTTP值,在这种情况下,HTTP值是包含所选ID的字符串数组。这些字符串不直接映射到Locations对象(即数字1不能直接转换为ID为1的Locations对象。)
最好的办法是在View Model中输入一个类型为string或int的位置ID列表以接受发布的值,然后在post方法中创建Location对象并用正确的ID填充它们。
仅供参考,您的代码工作的原因是您绕过模型绑定并直接转到Request.Form集合。您会注意到绑定的Article对象将没有任何Location对象。
编辑:
即使没有这个问题,我甚至都看不到你的代码是如何工作的。您的ArticleFormViewModel没有无参数构造函数,因此在模型绑定中会失败(除非您有自定义模型绑定器)。
无论如何,你要做的就是这个(注意,如果你想在渲染视图时选择它们,你必须填充SelectedLocationIDs):
public class ArticleFormViewModel
{
...
List<int> SelectedLocationIDs { get; set; }
...
}
然后,在您的视图中,您有:
@Html.ListBoxFor(m=>m.SelectedLocationIDs,
new MultiSelectList(Model.Locations,"LocationID","Name"))
在Post方法中,您可以使用以下内容代替调用Request.Form的代码:
foreach(var locationID in article.SelectedLocationIDs) {
... // look up your locations and add them to the model
}