2013-04-22 37 views
2

我有一个简单的模型,它使用多选择列表框作为许多EF关系。MVC模型状态验证在列表框中失败

在我Create行动,我得到了错误

从类型“System.String”的参数转换为键入“MyProject.Models.Location”失败,因为没有类型转换器可以将这些类型之间的转换。

我有2种型号,文章和地点:

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; } 
    } 
} 

我有一个视图模型:

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); 

} 

这一切工作比较好,我的位置列表框正确填充:

enter image description here

如果我这样做不选择一个位置,然后我的模型正确保存。如果我选择一个或多个位置,然后我Model.IsValid检查失败,异常

从类型“System.String”的参数转换为键入“MyProject.Models.Location”失败,因为没有类型转换器可以转换之间这些类型。

但是,如果我删除了ModelState.IsValid检查,然后尽管出现错误,我的值都被正确地保存到数据库中 - 只是我失去了对模型标题等内容的验证。

希望有人能帮助!

回答

1

除非您创建了一个类型转换器,否则不能直接将列表框的结果直接绑定到这样的复杂对象上。原因在于MVC只能处理发布的HTTP值,在这种情况下,这是一个包含选定ID的字符串数组。这些字符串不会直接映射到您的位置对象(即编号1不能直接转换为ID为1的位置对象)。

最好的办法是在你的视图模型中有一个类型为string或int的位置ID列表来接受发布的值,然后在你的post方法中创建Location对象并填充正确的ID。仅供参考,您的代码工作原因是因为您绕过模型绑定并直接转到Request.Form集合。您会注意到绑定的Article对象不会有任何位置对象。

编辑:

我甚至不看你的代码,甚至会工作,没有这个问题。您的ArticleFormViewModel没有无参数的构造函数,因此在模型绑定中将失败(除非您有自定义模型绑定器)。

在任何情况下,你想做的事是这样的(注意,你必须填充SelectedLocationIDs,如果你希望在视图呈现他们被选中):

public class ArticleFormViewModel 
{ 
    ... 
    List<int> SelectedLocationIDs { get; set; } 
    ... 
} 

然后,在你的看法您有:

@Html.ListBoxFor(m=>m.SelectedLocationIDs, 
    new MultiSelectList(Model.Locations,"LocationID","Name")) 

在安置自己的方法,而不是调用的Request.Form的代码,你有这样的事情:

foreach(var locationID in article.SelectedLocationIDs) { 
    ... // look up your locations and add them to the model 
} 
+0

好吧,这使得很多的意义,我没有意识到,Request.Form绕过模型绑定。尽管我很努力地将您的评论与我的代码相适应,但您能否告诉我如何更改代码? – 2013-04-22 12:22:47

+0

@RobbieMills - 查看更新。 – 2013-04-22 18:22:40

+0

完美,极大地帮助我。 – 2013-04-23 00:53:12