ASP.NET MVC 的“为什么”与“怎么做”:第二部分






4.83/5 (38投票s)
ASP.NET MVC 模型绑定方法、基本思想、优点以及模型验证。
引言
我知道距离我写完第一部分已经有一段时间了。您现在可能已经阅读了很多关于 ASP.NET MVC 的资源。因此,我现在将跳过“怎么做”部分,而将更多地关注“为什么”。此外,在本系列文章中,我将演示一些 ASP.NET MVC 中提供的新特性。请记住,在这篇文章中,我提到的 MVC 都指的是 MVC 4。在本文中,我们将仅限于讨论模型。为什么使用模型、处理模型时应采取什么方法以及如何验证模型。
ASP.NET 上的 MVC
听起来有点过时且与主题无关,但即使现在,我仍然看到许多关于 Web 窗体开发者如何计划迁移到 MVC 的讨论。这是一个热门话题,因此我想分享一些这方面的见解。对于新开发者来说,MVC 很有吸引力,但对于那些考虑从 Web 窗体迁移到 MVC 的开发者来说,主要 concerns 是熟悉他们多年来习惯的功能。大多数 Web 窗体开发者的 concerns 是 ASP.NET Web 窗体框架在以下方面提供了很多功能:
- 安全性
- 调试
- 验证
- 状态管理
- 主母版页/用户控件
- RAD 控件
- 熟悉的语法
现在,常见的问题是 ASP.NET MVC 是否能满足这些需求?它能做到吗?
嗯,让我们看看 ASP.NET MVC 提供了什么。尽管它们的架构方法非常不同,但 ASP.NET MVC 和 Web 窗体实际上有很多共同之处。
关注点 | ASP.NET MVC | Web Form |
---|---|---|
安全 | 窗体/Windows 身份验证、Membership | 窗体/Windows 身份验证、Membership |
验证 | 数据注解 | 用于验证的服务器控件 |
状态管理 | 会话和配置文件状态 | 视图状态/Cookie/查询字符串 |
跨页面数据传输 | 从控制器到视图/从控制器到请求的 ViewData、ViewBag 和 TempData | 会话/查询字符串 |
健康监控 | 健康监控 | 健康监控 |
缓存 | 输出和数据缓存 | 输出和数据缓存 |
主页模板 | 布局页 (Layout Page) | 主页模板 |
用户控件 | 部分视图 | 用户控件 |
RAD 控件 | HTML 助手 | 服务器控件 |
语法 | Razor 视图引擎 | ASPX 视图引擎 |
Bingo,正如您所见,一个准备学习 ASP.NET MVC 的 Web 窗体开发者比他想象的已经更进一步了。在生产力方面,ASP.NET Web 窗体和 MVC 没有太大区别,我们可以当然可以提出 RAD 控件的问题。我同意 RAD 控件可以加快开发速度,但想想 UX 设计师如何在没有开发人员参与的情况下玩转视图。开发人员不必花费精力使用 RAD 控件来实现酷炫的 UX。结合这些最终的开发工作,Web 窗体和 MVC 之间的差异非常小。此外,如今有许多优秀的框架,如 AngularJs/Backbone js,可以简化开发工作。生产力是希望从 Web 窗体迁移到 MVC 的开发者的主要 concerns 之一。
这并不奇怪,就像 Web 窗体一样,ASP.NET MVC 是建立在 ASP.NET 平台之上的。因此,这两个框架都依赖 .NET 语言 C# 和 Visual Basic .NET 与 .NET Framework 进行交互,并且可以访问使用 .NET Framework 支持的任何其他语言开发的已编译程序集。更有趣的是,在开发 MVC 时,您会看到非常熟悉的 *web.config* 和 *Global.asax*。如果您在 Google 上搜索,您可能会找到成千上万关于这些比较的资源。
使用模型
现在您可能会问一个显而易见的问题:数据库优先、模型优先还是代码优先?这些都是什么,它们与 MVC 有什么关系?说得好,但要回答这个问题,让我们先退一步,回顾一下第一部分,其中讨论了 **M**VC 的模型部分。让我们来回忆一下。
模型是 MVC 应用程序中与应用程序数据域交互并从数据库检索/存储模型状态的部分。
所以模型是您应用程序的核心(从业务角度来看),它处理最重要的元素:“数据”。模型代表您的数据结构,并将包含帮助您执行 CRUD(创建/检索/更新/删除)操作的函数。现在,根据架构、应用程序类型/范围、风险分析,我们需要不同的方法来实现模型实体。这就是 EF(Entity Framework)发挥作用的地方。使用 EF,我们可以灵活地按需处理模型。
Entity Framework 提供了三种定义实体模型的方法:使用数据库优先工作流、模型优先工作流,以及最后但同样重要的是,代码优先方法。由于对正在编写或实现的类拥有完全控制权,代码优先的模型设计方法正成为一种流行的选择。但这并不意味着数据库优先或模型优先不适合使用。诀窍在于知道何时使用何种方法;如果您能够分析您的应用程序场景/范围/风险并选择最适合您场景的方法,在我看来,这才是酷炫的。让我们深入到我们的主要话题。
为什么是数据库优先
考虑这样一个场景:一个现有的企业数据库有许多表,每个表都有许多列。它们没有您喜欢的规范化,但您也无话可说,因为这是一个现有的数据库。当业务正在进行时,没有人会允许您破坏和重构数据库。在这种情况下,您的应用程序的实体模型必须与现有数据库兼容;您不能弯曲数据库来适应您偏好的对象模型,换句话说,您的双手被束缚了。数据库优先在这种场景中很受欢迎,也称为“遗留系统”场景,适用于那些构建应用程序访问现有生产数据库(包含大量表和列)的开发人员。
何时采用数据库优先方法,在论坛、群组、用户聚会等地方有不同的看法。我不想重复造轮子,我喜欢总结它们,这里有一个列表:基于 DBA 设计的现有数据库构建的遗留系统,独立开发,或者如果您有现有数据库。
- 您会让 EF 为您创建实体,在修改映射后,您将生成 POCO 实体。
- 如果您希望 POCO 实体具有其他功能,您必须修改 T4 模板或使用部分类。
- 数据库定义您的领域模型。
- 大量的业务逻辑驻留在数据库端,以实现更好的可维护性。
现在的问题是,使用数据库优先是否不好?许多专家有自己的观点,我的观点是,尽管对象模型不尽如人意,但如果应用程序按预期运行,那么就没有理由对此不满意。我们在这里讨论的每种方法都有其优点和缺点。
优点
- RAD 开发方法,EF 将包含现有数据库的所有常用元数据、CSDL、SSDL 以及它们之间的映射,就像模型优先一样。
- 在处理大量数据时,可以首先专注于数据库设计。
- 数据库不仅仅是存储,还可以用于实现业务逻辑。
缺点
- 对 EF 生成的代码没有控制权。
- EF 生成的代码太复杂,无法自定义。
- 对 EF 生成的实体控制较少。
- 强制执行自定义实体级验证需要 T4 模板的专业知识。
为什么是模型优先
尽管数据库优先方法是后续方法,但不断变化的开发场景要求进行领域驱动开发,模型优先带来了 DDD 的精髓,但并非真正如此。模型优先设计开始流行,其中数据库模式基于模型。尽管概念上数据库优先和模型优先方法不同,但从实现的角度来看,差别不大。两者最终都会生成大量自动生成的代码。
与数据库优先一样,何时采用此方法有各种各样的答案,我的总结版本是:
- 在我看来,如果您是设计者粉丝(=您不喜欢编写代码或 SQL),它很受欢迎。
- 您将“绘制”您的模型,然后让工作流生成您的数据库脚本,T4 模板生成您的 POCO 实体。您将失去对实体和数据库的部分控制,但对于小型简单项目,您将非常高效。
- 如果您希望 POCO 实体具有其他功能,您必须修改 T4 模板或使用部分类。
- 对数据库的手动更改很可能会丢失,因为您的模型定义了数据库。如果您安装了数据库生成包,这会更好。它将允许您更新数据库架构(而不是重新创建)或更新 VS 中的数据库项目。
优点
- 数据库不仅仅是存储,还可以用于实现业务逻辑。
- 创建符合您的业务域的模型及其关系的灵活性。
- 最小化由生成的对象模型(代码)或数据库中的更改引起的“涟漪”效应。
缺点
- 每次创建数据库时,数据库的内容都会被重新生成,导致数据丢失,因为它每次都从模型重新生成一切。
- 一旦数据库架构发布,许多情况下可以直接修改数据库,因此模型会过时,并且必须以数据库优先的方式从数据库进行更新。
为什么是代码优先
在代码优先方法中,您完全避免使用 Visual Model Designer (EDMX)。您首先编写 POCO 类,然后根据这些 POCO 类创建数据库。这是领域驱动设计 (DDD) 的理想选择。代码优先使用类和属性分别标识表和列。代码优先有许多优点。首先,开发人员不再因为没有数据库而延迟。您现在可以创建数据库结构并开始编码,所有内容都保留在同一个解决方案中。其次,没有自动生成的代码;开发人员可以完全控制每个类。最后,数据访问层中的所有内容都保持简单,因为没有需要更新或维护的 *.EDMX* 模型。
优点
- 易于集成模型验证。
- EF modeling 经常会出现一些奇怪的错误,例如当您尝试重命名关联属性时,它需要与底层元数据匹配 - 非常不灵活。
- 回到代码优先,您拥有完全的控制权,无需处理所有隐藏的复杂性和未知数。
- 启用迁移可以跟踪架构更改,因此可以在模型更改的同时无缝部署升级/降级。
我是代码优先的粉丝,所以我猜我对缺点的看法是偏颇的。
缺点
- 不支持可视化模型设计器 (EDMX)。
- 无自动生成代码。
- 对数据库的手动更改很可能会丢失,因为您的代码定义了数据库。
关于何时使用代码优先没有硬性规定,但总的来说,您可以使用代码优先,因为:
- 完全控制代码(没有难以修改的自动生成代码)。
- 非常适合具有复杂业务需求的应用。
- 非常受欢迎,因为硬核程序员不喜欢设计器,并且在 EDMX XML 中定义映射太复杂。
- 如果您期望数据库在您的应用程序中的作用仅仅是存储。EF 将处理创建,而您不想知道它是如何工作的。
模型验证
正如我们在以上三种方法中讨论过的,模型对象处理数据并在该数据上执行业务逻辑。模型是应用程序特定的,因此 ASP.NET 框架对模型对象的构造没有限制。但是我们可以对模型强制执行自定义验证。添加此类验证最常见和最方便的方法是代码优先,因为这种方法确保了对模型的完全控制,并且有两种流行的验证方法:DataAnnotation 和 FluentValidation。DataAnnotation 和 FluentValidation 都服务于类似的目的。它们确保已分配给对象属性的值满足应适用的业务规则。
DataAnnotation | FluentValidation |
---|---|
内置于 .NET Framework 的验证支持,并且只需最少的代码即可集成。 | 高级验证支持,这是数据注释无法实现的。 |
不打算与 DB/Model First 一起使用,但可以通过一些变通方法(伙伴类)提供支持。 | 仅支持代码优先方法。 |
这是否意味着数据库优先或模型优先方法不需要任何模型验证?在使用数据库优先或模型优先时,EDMX 文件具有相同的目的——它包含模型的所有详细信息和映射定义。您定义的任何限制(或约束)都将通过 EDMX 映射定义转换为数据库/数据库模型,因此您无需额外努力来定义自己的限制。只有在使用代码优先时,Fluent API 或数据注释和约定才会取代 EDMX 文件。
测试模型
MVC 的一个主要关注点一直是测试。因此,本文在不讨论如何对模型进行单元测试的情况下是不完整的。只是提前告知,我将尝试展示正面和负面测试。希望将来能详细讨论。例如,我们以一个带有 DataAnnotation 的非常简单的类为例,并且只进行属性设置规则测试。供您参考,许多专家建议测试派生属性而不是测试每个属性。
public class User
{
public string FName { private get; set; }
public string LName { private get; set; }
[Required]
[StringLength(10)]
public string Name { get { return string.Format("{0} {1}", this.FName, this.LName); } }
}
现在,最后进行测试,为了简单起见,我只测试 Name 规则,我编写了两个测试:一个正面测试,一个负面测试。
正面测试:确保名称长度满足允许的长度
[TestMethod]
public void Name_Should_have_valid_length()
{
User _userToTest = new User( );
_userToTest.LName = "Sh."; //3 character
_userToTest.FName = "Iqbal"; //5 character
Assert.IsTrue(_userToTest.Name.Length <= 10);
}
负面测试:确保在名称超出允许长度时抛出错误 [TestMethod]
public void Name_Should_Fail_for_invalid_length()
{
User _userToTest = new User();
_userToTest.LName = "Shahriar"; //8 character
_userToTest.FName = "Iqbal"; //5 character
Assert.IsTrue(_userToTest.Name.Length <= 10);
}
为了有效的测试,应同时利用正面和负面测试。我知道这个例子非常小且基础,但对初学者来说很好,我希望在接下来的文章中展示更多详细的测试技巧。
参考和好读
- ASP.NET MVC Framework Unleashed
- 数据库优先演练
- 模型优先演练
- 代码优先演练
- 模型验证演练
- Fluent Validation 演练
- Data Annotations 演练
系列
- ASP.NET MVC 的“为什么”与“怎么做”:第一部分 - MVC 基础。
历史
- 版本 1.0。