为什么我的模型状态无效?
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 编辑的数据。 TransfersRemaining
和 CreatedDate
都将设置为其各自的默认值。同样,两个枚举值 StartingXI
和 Subs
将被清空,这将导致这些关系在您的数据库中被删除。此外,由于 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 设计的一些注意事项:
您的两个构造函数中有重复的逻辑。通过重载处理会好得多:
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;
}
但是,构造函数不是设置默认值的地方。重要的是,只要此 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; }
}
虽然它很小,但是像 getTransfersRemaining
和 getTeamName
这样的方法只是 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.
我有以下型号:
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 编辑的数据。 TransfersRemaining
和 CreatedDate
都将设置为其各自的默认值。同样,两个枚举值 StartingXI
和 Subs
将被清空,这将导致这些关系在您的数据库中被删除。此外,由于 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 设计的一些注意事项:
您的两个构造函数中有重复的逻辑。通过重载处理会好得多:
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; }
但是,构造函数不是设置默认值的地方。重要的是,只要此 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; } }
虽然它很小,但是像
getTransfersRemaining
和getTeamName
这样的方法只是 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.