让“肥”Razor MVC 视图“瘦身”的三步法
ASP.NET MVC 视图让您能够使用 Razor 视图引擎,并具有编写 C# 代码的灵活性。然而,如果您编写了过多的 C# 代码,最终会得到需要“瘦身”的“肥胖 Razor”,否则您可能会面临关注点分离攻击的风险。
首先,我来定义一下我将引用的术语
表示代码:视图中显示视觉元素所必需的最少代码,不涉及任何业务决策。这是一个虚构的简单示例。
// Business Logic Code. This code understands how the business works
// (don't do this)
@if (Model.IsMonthly && !Model.IsPaid && Model.Payment >= 12 && ...)
{
<p>Some warning message</p>
}
// Presentation Code. This code is only concerned with whether to
// show or hide a UI element.
@if (Model.ShouldWarn)
{
<p>Some warning message</p>
}
肥胖 Razor:我创造了这个术语来定义 ASP.NET Razor 视图中包含大量非表示代码的 Razor/C# 代码。这仅指 Razor/C# 代码,而不是 HTML/CSS/JS。
模型:这是一个术语。但在此上下文中,它是数据表示对象,例如数据库中的记录。
视图模型:它们是数据传输对象,旨在特定于视图,并携带视图所需的确切内容,不多不少。通常,视图模型是不可重用的,并且每个视图模型都旨在与一个视图紧密耦合,但是,根据您的操作,也会有例外。
为了让您体会模型和视图模型
// An example of a model
public class Person
{
public Guid PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public short YearOfBirth { get; set; }
public DateTime LastAuth { get; set; }
// This might represent a one-to-many db relationship
public Order[] Orders { get; set; }
}
// An example of a viewmodel
public class ProfileViewModel
{
// Doesn't necessarily need an id
// Combined first and last name
public string Name { get; set; }
// Calculated from YearOfBirth
public short Age { get; set; }
// E.g. "One day ago" and constructed from LastAuth
public string LastSeen { get; set; }
// Calculated by projections on Orders
public short NumberOfAllOrders { get; set; }
public short NumberOfUndeliveredOrders { get; set; }
}
为什么“肥胖”的 Razor 视图会带来问题?
在讨论解决方案之前,我们需要同意存在一个问题。
业务逻辑的分布(非集中化)
“肥胖 Razor”视图承载的批评与旧的经典 ASP(以及当前的 PHP)类似,即没有关注点分离。这段代码的关注点是显示视觉效果,而不是执行业务逻辑。
您的域的业务逻辑应集中化,“肥胖 Razor”视图突出了视图中业务逻辑的使用,因此您正在将更多控制权交给本应是一个“哑巴”层,并违反了“权力在错误的手中”原则。您将面临易碎的业务逻辑,它将更频繁地出错。
由于本文的范围,我只想说“这很糟糕,不要这样做”,而不会深入探讨这个话题,但不要只听我的话,这里是 Uncle Bob 的清洁架构。
单元测试
您可能以前听说过,视图是不可单元测试的(好吧,您可以使用某些工具如Razor Generator 进行单元测试,但这并不常见),或者更准确地说,不应该对其进行单元测试,而应依赖于其他类型的测试,如 Visual Studio Testing Tools、Selenium 或 Capybara 等 Web 测试。因此,“肥胖 Razor”视图意味着更多无法进行单元测试的代码。
重构
您有多少次更改代码后编译通过,直到在浏览器中打开页面?然后,您的代码重构并未传播到视图,因为它将视图视为文本。今天,借助 ReSharper 等工具,这种情况不那么戏剧化了,但仍然是一个问题。编写更少的 C# 代码可以减小问题规模,但无法消除它。
为什么视图会变成“肥胖 Razor”视图?
这是我从行业中观察到的症状,如果您有更多原因,欢迎在评论中补充。
使用通用模型而非特定视图模型
MVC 入门教程试图通过从数据库加载记录,将其传输到 OR Mapper(Entity Framework、NHibernate 等)的对象,然后将其传递给渲染它的视图来展示 MVC 的简单性。以 MVC 教程构建博客为鉴,我指的是您。
这对初学者来说是一个很好的例子,可以让他们快速上手,但只适用于简单的应用程序。为什么?如果您的视图没有获得它想要的确切内容,那么它将不得不对模型进行一些检查,因此会产生代码,更多的代码!如果您将视图视为“哑巴”,并提供它确切想要的内容,那么您将减少它需要任何非表示代码的需求。
让您的“肥胖 Razor”回归正轨
Big Boss Man 是我早期青少年摔跤的偶像(他强壮又酷),但 Razor Ramon 更适合这篇文章。
现在我触及了围绕这个主题的一些必要概念,我可以强调一些使 Razor 视图“苗条”的步骤。
1. 为您的视图使用视图模型
不要直接将数据库中的模型放入视图。这会将您的视图与您的实体耦合,并可能引入业务逻辑代码。
我最喜欢这样做的方式是使用CQRS类型的查询,本文很好地介绍了 CQRS:CQRS 简介。我精确地查询我的视图需要的内容到视图模型中,没有中间模型(或 DTO),可以看看Dapper – .Net 的简单对象映射器。
如果您有不同的架构或遗留代码,也许可以有一个层来将模型映射到 `viewmodel`,可以看看AutoMapper,这可能会有帮助。
2. 尝试区分业务逻辑和表示代码
编写视图时,始终问“这是表示代码还是业务逻辑?”这是一个棘手的问题,因为在某些情况下,两者之间可能只有一线之隔。有时,我会征求第二意见,并鼓励健康的讨论。
3. 将重复的表示模式打包到 HTML Helpers 和 Partial Views 中
有时,人们会在不同的视图中反复编写相同的表示代码。我也这样做,遵循敏捷原则“做最简单有效的,然后重构”,然后当我注意到模式形成时,我将其重构为 HTML Helper 或 Partial View。
结论
俗话说“少即是多”,您编写的代码越少,需要维护的代码就越少,并且您编写的正确代码将使您在接手项目的开发人员了解您的意图时保持安全。
我多次审查了这篇文章以缩短篇幅,但是,这是一个很大的话题,仍然不完整。如果您有任何想分享的观点,请在评论中留言。