为什么我的模型状态无效?

Why is my model state not valid?

我有以下型号:

public class Team
{
    public int Id { get; set; }

    [Required]
    [MaxLength(100)]
    public string Name { get; set; }

    [Required]
    public string Owner { get; set; }
    public int TransfersRemaining { get; set; }
    public DateTime DateCreated { get; set; }

    public IEnumerable<Player> StartingXI { get; set; }
    public IEnumerable<Player> Subs { get; set; }

    public Team() {
        TransfersRemaining = 5;
        StartingXI = Enumerable.Empty<Player>();
        Subs = Enumerable.Empty<Player>();
        DateCreated = DateTime.Now;
    }

    public Team(String teamName, String userId) 
    {
        Name = teamName;
        TransfersRemaining = 5;
        Owner = userId;
        StartingXI = Enumerable.Empty<Player>();
        Subs = Enumerable.Empty<Player>();
        DateCreated = DateTime.Now;
    }

    public int getTransfersRemaining() {
        return TransfersRemaining;
    }

    public string getTeamName()
    {
        return Name;
    }
}

我有以下 ActionResult:

[HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create([Bind(Include = "Name, Owner")] Team team)
    {
        team.Owner = "bob";
        if (ModelState.IsValid)
        {
            teamRepository.Insert(team);
            teamRepository.Save();
            return RedirectToAction("Index");
        }



        foreach (ModelState modelState in ViewData.ModelState.Values)
        {
            foreach (ModelError error in modelState.Errors)
            {
                Response.Write(error);
            }
        }

        return View(team);
    }

在调试时,我发现错误是 "The Owner field is required."

当然我已经提供了 team.Owner 检查我的团队对象,我可以看到 Owner = "bob" 和所有其他属性都已正确设置。

知道问题出在哪里吗?

ModelState.IsValid是基于ModelState的内容,不是你的实体实例。因此,在您的操作中首次设置 team.Owner 并不能否定它未被 post 编辑并因此不存在于 ModelState.[=36 中的事实=]

但是,您还有许多关键问题需要在这里解决。首先,通过结合使用 Bind 和直接保存模型绑定器创建的实体实例,您将清除所有未被 post 编辑的数据。 TransfersRemainingCreatedDate 都将设置为其各自的默认值。同样,两个枚举值 StartingXISubs 将被清空,这将导致这些关系在您的数据库中被删除。此外,由于 Id 不是 posted,它将是 int 的默认值,0,这将导致 Entity Framework 创建一个新记录,而不是更新一个现有的。

None 这些问题必然是初始创建的问题,但一旦将其应用到您的编辑中,您就会搞砸所有数据。你应该断然永远不要使用 Bind。这太糟糕了。说真的,只是不要。这是 Microsoft 为解决直接使用实体可能导致的过度 post 问题而进行的愚蠢尝试,但它导致的问题多于它解决的问题。此外,还有一个更好更简单的解决方案:不要使用实体。相反,创建一个仅包含您希望用户能够编辑的属性的视图模型,然后将这些 posted 值映射到您的实体实例。这样一来,用户就不可能操纵 post 数据,因为您可以明确控制从 post 中保存和不保存的内容。 (有关为什么 Bind 所以 糟糕的更多解释,请参阅:https://cpratt.co/bind-is-evil/。)

接下来,我假设您想要 Player 与此处的枚举项建立实际关系:例如,您希望该关系保留在数据库中。 IEnumerable 不会发生这种情况。您需要使用 ICollection 代替:

public ICollection<Player> StartingXI { get; set; }
public ICollection<Player> Subs { get; set; }

此外,这不是必需的,有些人可能认为这不是一个好主意,但如果您打算使用延迟加载,这些属性将需要 virtual 关键字。 not 允许延迟加载是完全有效的,甚至可能是推荐的选择,但您必须知道这些集合将为空,除非您在需要时急切或显式加载它们。这通常会使人感到困惑,因此最好注意您在这里所做的事情并了解其利弊。

最后,关于您的 class 设计的一些注意事项:

  1. 您的两个构造函数中有重复的逻辑。通过重载处理会好得多:

    public Team()
        : this(null, null)
    {
    }
    
    public Team(String teamName, String userId) 
    {
        Name = teamName;
        TransfersRemaining = 5;
        Owner = userId;
        StartingXI = Enumerable.Empty<Player>();
        Subs = Enumerable.Empty<Player>();
        DateCreated = DateTime.Now;
    }
    
  2. 但是,构造函数不是设置默认值的地方。重要的是,只要此 class 被实例化,这将 运行,包括当 Entity Framework 作为查询结果创建实例时。例如,如果您要从数据库中提取一组现有团队,所有这些属性都将重置为这些默认值,而不是数据库中记录存在的值。如果需要默认值,则需要使用自定义 getter 和 setter 或 C# 6 的初始化语法:

    C#6+

    public DateTime DateCreated { get; set; } = DateTime.Now;
    

    上一个 C#

    private DateTime? dateCreated;
    public DateTime DateCreated
    {
        get { return dateCreated ?? DateTime.Now }
        set { dateCreated = value; }
    }
    

    列表和其他复杂类型有点困难,因为您只想在它们为空时实例化它们。 C# 6 初始化器语法在这里是相同的,但对于以前版本的 C#,您应该使用:

    private IEnumerable<Player> subs;
    public IEnumerable<Player> Subs
    {
        get
        {
            if (subs == null)
            {
                subs = new List<Player>();
            }
            return subs;
        }
        set { subs = value; }
    }
    
  3. 虽然它很小,但是像 getTransfersRemaininggetTeamName 这样的方法只是 return 一个 属性 值是多余的,只会增加熵代码。如果您遵循 TDD,那么这些现在是您必须维护测试的东西,而 属性 本身不需要测试。这些属性本身是您实体 public API 的一部分;你不需要任何其他东西。如果你这样做是为了满足一个接口,那实际上是违反了SOLID中的I,接口隔离原则:

    A client should never be forced to implement an interface that it doesn't use or clients shouldn't be forced to depend on methods they do not use.