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

时间:2017-07-14 15:43:49

标签: c# asp.net-mvc modelstate

我有以下型号:

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

在调试时,我看到错误是 &#34;所有者字段是必需的。&#34;

当然,我已经向team.Owner提供了这一点 检查我的团队对象,我可以看到所有者=&#34; bob&#34;并且所有其他属性都已正确设置。

任何想法是什么问题?

1 个答案:

答案 0 :(得分:3)

ModelState.IsValid基于ModelState的内容,而不是您的实体实例。因此,在您的操作中首次设置team.Owner并不能否定它没有发布,因此在ModelState中不存在。

但是,您还需要解决许多重要问题。首先,通过使用Bind并直接保存由模型绑定器创建的实体实例的组合,您将会烧掉任何未发布的数据。 TransfersRemainingCreatedDate都将设置为各自的默认值。同样地,将清空两个枚举StartingXISubs,这将导致在数据库中删除这些关系。此外,由于未发布Id,因此它将是int 0的默认值,这将导致Entity Framework创建新记录,而不是更新现有记录。

这些问题都不一定是初始创建的问题,但只要您将其应用到编辑中,您就会将所有数据搞砸。你应该坚持不要永远使用Bind。太可怕了。同样严肃,不要。微软在解决直接与实体合作的过度问题方面的愚蠢尝试可能导致,但它导致的问题多于解决的问题。此外,还有一个更好,更简单的解决方案:不使用该实体。而是创建一个视图模型,该模型仅包含您希望用户能够编辑的属性,然后将这些已发布的值映射到您的实体实例。这样,用户无法操纵帖子数据,因为您可以明确控制从帖子中保存和不保存的内容。 (有关Bind为何的原因的其他说明,请查看:https://cpratt.co/bind-is-evil/。)

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

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

此外,它不是必需的,有些人可能认为这不是一个好主意,但如果您打算使用延迟加载,这些属性将需要virtual关键字。它是允许延迟加载的完全有效甚至建议的选择,但您必须知道这些集合将为null,除非您在需要时急切地或明确地加载它们。这往往会让人兴奋,所以关注你在这里所做的事并理解其优缺点是很好的。

最后,关于班级设计的一些注意事项:

  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. 但是,构造函数不是设置默认值的位置。重要的是,这将在此类实例化时运行,包括实体框架在查询时创建实例的时间。例如,如果要从数据库中提取一堆现有团队,则所有这些属性都将重置为这些默认值,而不是数据库中记录的值。如果你想要默认值,你需要使用自定义的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; }
    }
    

    列表和其他复杂类型有点困难,因为如果它们为null,您只想实例化它们。 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之类的方法仅返回属性值是多余的,只会为您的代码添加熵。如果您正在关注TDD,那么这些是您必须维护测试的内容,而该属性本身不需要测试。这些属性本身就是您实体的公共API的一部分;你不需要任何其他东西。如果你这样做是为了满足界面,那实际上违反了SOLID中的 I ,接口隔离原则:

      

    永远不应该强迫客户实现它不使用的界面,或者不应该强迫客户依赖他们不使用的方法。