我目前正在做一个 ASP.NET 的MVC项目。
团队中的一些开发人员希望将自动生成的数据库实体直接绑定到视图。
其他开发人员希望创建量身定制的视图模型并将其绑定到视图。
客观地说,这两种方法的优缺点是什么?
(我所说的"数据库实体"是指ORM框架生成的自动生成的类,例如LINQ to SQL,Entity Framework或LLBLGen)。
一定要在视图中使用视图模型,并使用类似 AutoMapper
的东西轻松地从实体创建视图模型。
缺点:
- 有时感觉就像是在复制代码,特别是当视图模型和实体具有完全相同的属性时
优点:
- 通常需要以更简单的格式(通常称为平展)表示对象,但需要在服务器端实现完全保真。这允许您在两者之间转换,而不会用表示垃圾来破坏您的领域模型。
- 聚合根通常具有许多与特定视图无关的值对象和其他实体,在视图模型中省略它们可以使其更易于使用。
- 您的实体将具有许多双向引用,这些引用在API方面是明智的,但是在为JSON,XML等序列化它们时会产生纯粹的地狱。 视图模型将消除这些循环引用。
- 您可能经常使用相同的实体,但以不同的方式用于不同的视图。试图在一种类型上平衡两种需求可能会造成巨大的混乱。
正统观念是永远不应该在视图中使用原始数据库实体。像任何规则一样,如果你非常了解你的实体并且你了解后果,它就会被打破,但是有很好的理由不违反该规则,特别是在团队中工作和使用代码时,这些代码将来将由可能不了解规则或实体的人维护。主要原因是:
-
ORM 延迟加载。假设您的客户有一个延迟加载的收集订单。您将客户传递到视图,它会迭代订单。您可以在"订单"表上获得 N*1 选择。但这也意味着您的数据库连接仍需要在视图中打开。有一种模式是人们使用"每个操作的事务",它在Action_Executed事件中释放数据库上下文,这发生在呈现视图之前。因此,您可能会在释放数据库后尝试访问数据库。即使你现在不这样做,将来有人可能会决定实现这种模式,因为它很流行。
-
视图模型的关注点与数据库模型不同。例如,通常使用验证属性修饰视图模型属性。这些通常是不同的,或者只涉及 UI 而不是数据库。如果绑定到数据库实体,您会发现所有这些 UI 都污染了数据库实体。
-
与 2 相关 - 视图模型的要求可能需要计算或派生属性。例如,由名字和姓氏构造的全名。这些东西最好保存在视图模型中。
-
您可以在独立于数据库的情况下对视图模型进行单元测试。ViewModels最终可能包含相当多的逻辑,这些逻辑需要进行单元测试。如果它未绑定到数据库(与 EF 实体一样),则更容易测试。
一般来说,创建和维护 ViewModels(即使没有 AutoMapper)不会产生开销,您会发现总体上这是一种更好的开发模式。我会推荐它用于除最简单的情况(例如静态数据的查找列表)之外的所有情况。
使用视图模型是唯一的方法,所以ORM实体没有优点:)视图模型不仅为视图提供数据,而且还定义视图的外观(通过定义模板)或验证方式(通过添加数据注释或实现 IDataErrorInfo)。
使用视图模型:
优点:
- 视图
- 模型仅包含视图所需的属性,不包含其他任何属性。
- 视图模型可能包含使用数据注释或 IDataErrorInformation 的特定验证规则。
- 视图模型可以组合来自不同数据库实体的值。
- 视图模型记录自身,不绑定到任何框架。
- 视图模型可保护您免受伪造的 POST 的侵害,这些 POST 包含的值不是以形式提供的,而是包含在 ORM 实体中。
- 您可以轻松指定视图模型的显示模板,并使用
DisplayFor
或EditorFor
帮助程序在许多位置重用它们。
使用 ORM 实体:
缺点:
- ORM实体已经包含数据注释,这可能会弄乱您的验证。示例:用户中的密码字段可能标记为
Required
,但仅更改基本用户信息时不需要密码字段。 - ORM实体与框架(实体框架)紧密相关,可能不容易实现规则。
- ORM 实体可以包含多个视图的属性,但很难为不同的视图分离验证规则。
- 使用具有延迟加载的 ORM 实体可能会导致您在呈现视图时执行 SQL 查询。它不应该发生。
- 使用 ORM 实体可能会导致对小型查询使用大型 SQL 查询。如果要显示包含名字和姓氏的下拉列表,应仅从数据库中检索名字和姓氏,而不是整个实体。
感谢您到目前为止的回答 - 它们对理解这两种方法的优缺点有很大帮助。我有一件事要补充,其他人没有提到。
过度发布攻击
直接绑定数据库实体的一个令人担忧的缺点是"过度发布攻击"。在这里,攻击者可以使用不比FireBug更高级的工具插入不打算由用户编辑但确实存在于数据库实体上的表单字段。
考虑"编辑我的个人资料"页面。您的视图可能如下所示:
@using(Html.BeginForm() {
<div>
@Html.LabelFor(x=> x.FirstName)
@Html.TextBoxFor(x=> x.FirstName)
</div>
<div>
@Html.LabelFor(x=> x.LastName)
@Html.TextBoxFor(x=> x.LastName)
</div>
<input type="Submit" value="Save" />
}
它将呈现以下 HTML:
<form action="/profile/edit" method="post">
<div>
<label for="FirstName">FirstName</label>
<input type="text" name="FirstName" value="" />
</div>
<div>
<label for="LastName">LastName</label>
<input type="text" name="LastName" value="" />
</div>
<input type="Submit" value="Save" />
</form>
使用FireBug,攻击者只需要在表单中插入一块HTML:
<input type="hidden" name="IsAdmin" value="true" />
。突然之间,用户能够以非常意外和有害的方式更改数据。
以下是一些更可怕的隐藏表单字段:
<input type="hidden" name="ShoppingCart.Items[0].Price" value="0.01" />
<input type="hidden" name="BankAccount.Balance" value="1000000" />
<input type="hidden" name="User.Administrator.Password" value="hackedPassword" />
哎哟!
信息取自:http://hendryluk.wordpress.com/tag/asp-net-mvc/
我曾经尝试开发一个应用程序,该应用程序直接在 ASP.NET 视图中使用NHibernate实体。我在延迟加载和延迟 SQL 执行时遇到了许多问题,直接从视图运行,而不是在业务逻辑层甚至控制器中运行。迁移到视图模型并使用自动映射器似乎解决了所有这些问题,并使应用程序更易于测试、调试和维护。
我还发现视图模型有助于在页面上保存我需要的所有相关数据。一些开发人员喜欢为此使用动态 ViewBag,但这不利于测试和调试。
特别是,当您想要从下拉列表中选取关联实体时,视图模型变得容易。
AutoMapper 是这个项目的救星,因为它节省了编写大量映射代码的时间,我所要做的就是创建视图模型,然后控制器从实体自动映射到视图模型。
不要向客户端公开后端实体。现实世界的应用程序有行为 - 而不是CRUD。如果您将数据实体绑定到视图,那么当客户端需要行为时,您深入研究泥泞的黑客只是时间问题。
我正要添加与黑客中国人完全相同的情绪。 另外,我要补充一点,使用 FK 的查找列表,您只需要使用视图模型,因为实体模型只需在该表中保存指向单个 id 的指针。视图模型允许您将所需的填充列表传递到视图中 - 瞧。
此外,视图模型可以在需要时包含谨慎的逻辑,实体模型绝对不是这种情况。此外,您的验证可能会因视图的使用而异,因此可以根据"视图"要求应用不同的验证。
视图模型的目的主要是关注点分离 - 将视图与模型的实现细节分离。
在视图中使用数据库实体(尤其是表单)是一个巨大的安全问题。 采用以下 POCO 对象
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string Email { get; set; }
public bool IsAdmin { get; set; }
}
现在假设您正在呈现一个允许用户更改其电子邮件的视图。 使用 Db 实体而不是视图模型时,用于处理表单结果的 MVC 方法如下所示:(除非您不使用模型绑定,在这种情况下,您将为自己做更多工作)
public class HomeController : Controller
{
[HttpPost]
public ActionResult ChangeEmail(User user)
{
//....
}
}
Asp.net 中的模型绑定通过查找与模型中属性名称匹配的 GET 或 POST 参数来工作。 因此,用户所要做的就是将IsAdmin=true
添加到 POSt 参数和 viola,传递给 ChangeEmail
函数的模型会将 IsAdmin 属性设置为 true,这很容易被意外添加到数据库中,让用户可以自由访问他们无权更改的更改数据。
这适用于用户权限、更改谁拥有实体(使您的问题与我而不是您相关联)、更改原始创建日期等......