【问题标题】:MVC Model State Validation fails on Listbox列表框上的 MVC 模型状态验证失败
【发布时间】:2026-01-24 07:55:01
【问题描述】:

我有一个简单的模型,它使用多选列表框来实现多对多 EF 关系。

在我的 Create 操作中,我收到了错误

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

我有 2 个模型,一个文章和一个位置:

文章.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 检查,那么尽管出现错误,但我的值都已正确保存到数据库中 - 只是我失去了对模型标题等内容的验证。

希望有人能提供帮助!

【问题讨论】:

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


    【解决方案1】:

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

    您最好的选择是在您的视图模型中包含一个字符串或 int 类型的位置 ID 列表以接受发布的值,然后在您的 post 方法中创建位置对象并用正确的 ID 填充它们。

    仅供参考,您的代码工作的原因是因为您绕过模型绑定并直接进入 Request.Form 集合。您会注意到绑定的 Article 对象将没有任何 Location 对象。

    编辑:

    即使没有这个问题,我什至看不到您的代码将如何工作。您的 ArticleFormViewModel 没有无参数构造函数,因此模型绑定将失败(除非您有自定义模型绑定器)。

    无论如何,您想要做的是(注意,如果您希望在呈现视图时选择它们,则必须填充 SelectedLocationID):

    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
    }
    

    【讨论】:

    • 好吧,这很有意义,我没有意识到 Request.Form 绕过了模型绑定。不过,我正在努力使您的评论适应我的代码,您能告诉我如何更改代码吗?
    • 完美,帮了我很大的忙。