我了解 MVC 中关注点分离的“正确”结构是拥有用于构建 View 的 View 模型和用于持久保存在所选存储库中的单独数据模型。我开始尝试使用 MongoDB,并且开始认为这在使用无模式、NO-SQL 样式的数据库时可能不适用。我想把这个场景展示给 stackoverflow 社区,看看大家的想法。我是 MVC 的新手,所以这对我来说很有意义,但也许我忽略了一些东西......
这是我的讨论示例:当用户想要编辑他们的个人资料时,他们会转到 UserEdit View ,该 View 使用下面的 UserEdit 模型。
public class UserEditModel
{
public string Username
{
get { return Info.Username; }
set { Info.Username = value; }
}
[Required]
[MembershipPassword]
[DataType(DataType.Password)]
public string Password { get; set; }
[DataType(DataType.Password)]
[DisplayName("Confirm Password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
[Required]
[Email]
public string Email { get; set; }
public UserInfo Info { get; set; }
public Dictionary<string, bool> Roles { get; set; }
}
public class UserInfo : IRepoData
{
[ScaffoldColumn(false)]
public Guid _id { get; set; }
[ScaffoldColumn(false)]
public DateTime Timestamp { get; set; }
[Required]
[DisplayName("Username")]
[ScaffoldColumn(false)]
public string Username { get; set; }
[Required]
[DisplayName("First Name")]
public string FirstName { get; set; }
[Required]
[DisplayName("Last Name")]
public string LastName { get; set; }
[ScaffoldColumn(false)]
public string Theme { get; set; }
[ScaffoldColumn(false)]
public bool IsADUser { get; set; }
}
注意到 UserEditModel 类包含一个继承自 IRepoData 的 UserInfo 实例? UserInfo 是保存到数据库的内容。我有一个通用存储库类,它接受任何继承 IRepoData 形式的对象并保存它;所以我只需调用 Repository.Save(myUserInfo)
就完成了。 IRepoData 定义了 _id(MongoDB 命名约定)和时间戳,因此存储库可以基于 _id 更新插入,并根据时间戳检查冲突,以及对象刚刚保存到 MongoDB 的任何其他属性。在大多数情况下, View 只需要使用 @Html.EditorFor
就可以了!基本上, View 需要的任何东西都会进入基本模型,只有存储库需要的任何东西都只需获得 [ScaffoldColumn(false)]
注释,其他所有东西在两者之间都是通用的。 (顺便说一句 - 用户名、密码、角色和电子邮件被保存到 .NET 提供商,因此它们不在 UserInfo 对象中。)
此方案的巨大优势有两个...
我可以使用更少的代码,因此更容易理解、开发速度更快、更易于维护(在我看来)。
我可以在几秒钟内重新考虑...如果我需要添加第二个电子邮件地址,我只需将它添加到 UserInfo 对象 - 它会添加到 View 中并保存只需向对象添加一个属性即可将其添加到存储库。因为我使用的是 MongoDB,所以我不需要更改我的数据库架构或弄乱任何现有数据。
鉴于此设置,是否需要制作单独的模型来存储数据?大家认为这种方法的缺点是什么?我意识到显而易见的答案是标准和关注点分离,但是你能想到任何现实世界的例子来证明这会导致一些令人头疼的问题吗?
还值得注意的是,我正在一个由两个开发人员组成的团队中工作,因此很容易看到好处而忽略了一些标准的弯曲。您认为在较小的团队中工作在这方面会有所不同吗?
最佳答案
无论使用何种数据库系统,MVC 中的 View 模型的优势都存在(即使您不使用该系统也是如此)。在简单的 CRUD 情况下,您的业务模型实体将非常接近地模仿您在 View 中显示的内容,但在基本 CRUD 之外的任何情况下,情况都不会如此。
其中一件大事是业务逻辑/数据完整性问题,使用与您在 View 中使用的相同的类进行数据建模/持久性。以您拥有DateTime DateAdded
的情况为例用户类中的属性,以表示添加用户的时间。如果您提供的表格直接连接到您的 UserInfo
类你最终会得到一个 Action 处理程序,如下所示:
[HttpPost]
public ActionResult Edit(UserInfo model) { }
您很可能不希望用户在添加到系统时能够进行更改,因此您的第一个想法是不要在表单中提供字段。
但是,您不能依赖它,原因有两个。首先是 DateAdded
的值将与执行 new DateTime()
时得到的结果相同或者它将是 null
(对于这个用户来说,任何一种方式都是不正确的)。
第二个问题是用户可以在表单请求中欺骗它并添加 &DateAdded=<whatever date>
到 POST 数据,现在您的应用程序会将数据库中的 DateAdded 字段更改为用户输入的任何内容。
这是设计使然,因为 MVC 的模型绑定(bind)机制会查看通过 POST 发送的数据,并尝试自动将它们与模型中的任何可用属性连接起来。它无法知道发送过来的属性不是原始形式,因此它仍会将其绑定(bind)到该属性。
ViewModel 没有这个问题,因为您的 View 模型应该知道如何将自己转换为数据实体/从数据实体转换,并且它没有 DateAdded
要欺骗的字段,它只有显示(或接收)其数据所需的最少字段。
在您的确切场景中,我可以通过 POST 字符串操作轻松重现这一点,因为您的 View 模型可以直接访问您的数据实体。
直接在 View 中使用数据类的另一个问题是,当您试图以一种与数据建模方式不相符的方式呈现 View 时。例如,假设您有以下用户字段:
public DateTime? BannedDate { get; set; }
public DateTime? ActivationDate { get; set; } // Date the account was activated via email link
现在假设作为管理员,您对所有用户的状态感兴趣,并且您希望在每个用户旁边显示一条状态消息,并根据该用户的状态为管理员提供不同的操作。如果您使用数据模型,您的 View 代码将如下所示:
// In status column of the web page's data grid
@if (user.BannedDate != null)
{
<span class="banned">Banned</span>
}
else if (user.ActivationDate != null)
{
<span class="Activated">Activated</span>
}
//.... Do some html to finish other columns in the table
// In the Actions column of the web page's data grid
@if (user.BannedDate != null)
{
// .. Add buttons for banned users
}
else if (user.ActivationDate != null)
{
// .. Add buttons for activated users
}
这很糟糕,因为您现在的 View 中有很多业务逻辑(被禁止的用户状态总是优先于激活的用户,被禁止的用户由具有禁止日期的用户定义,等等...)。它也复杂得多。
相反,一个更好(至少恕我直言)的解决方案是将您的用户包装在一个 ViewModel 中,该 ViewModel 具有对其状态的枚举,并且当您将模型转换为 View 模型时( View 模型的构造函数是一个不错的选择)这)您可以插入一次业务逻辑以查看所有日期并确定用户应该处于什么状态。
那么你上面的代码就简化为:
// In status column of the web page's data grid
@if (user.Status == UserStatuses.Banned)
{
<span class="banned">Banned</span>
}
else if (user.Status == UserStatuses.Activated)
{
<span class="Activated">Activated</span>
}
//.... Do some html to finish other columns in the table
// In the Actions column of the web page's data grid
@if (user.Status == UserStatuses.Banned)
{
// .. Add buttons for banned users
}
else if (user.Status == UserStatuses.Activated)
{
// .. Add buttons for activated users
}
在这个简单的场景中,这可能看起来不像是更少的代码,但是当确定用户状态的逻辑变得更加复杂时,它使事情更易于维护。您现在可以更改如何确定用户状态的逻辑,而无需更改数据模型(您不应该因为查看数据的方式而更改数据模型),并且它将状态确定保持在一个位置。
关于asp.net-mvc - MVC 和 NOSQL : Saving View Models directly to MongoDB?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6497996/