Razor 页面是 ASP.NET Core 2.0 中新增的功能。借助此功能,可以在 ASP.NET Core 应用程序内更轻松地整理代码,同时拉近实现逻辑和视图模型与视图实现代码的距离。
此外,还可以更轻松地开始开发 ASP.NET Core 应用程序,但这并不意味着经验丰富的 .NET 开发者就应该忽略 Razor 页面。使用 Razor 页面,还可以简化更大更复杂的 ASP.NET Core 应用程序。
模型-视图-控制器 (MVC) 模式是 Microsoft 自 2009 年以来一直支持的成熟 UI 模式,以方便开发者开发 ASP.NET 应用程序。此模式具有诸多优势,可以帮助应用程序开发者实现关注点分离,从而开发更易的软件。
很遗憾,此模式是在默认项目模板中实现,通常会生成大量文件和文件夹,这可能会增加开发阻力,尤其是在应用程序不断增长时。
在 2016 年 9 月刊的文章 () 中,我介绍了此问题的一种解决方法,即功能切片。Razor 页面通过另一种全新方式解决了相同问题,尤其适用于从概念上来讲以页面为依据的方案。如果只有几乎就是静态的视图或只需执行 OST-Redirect-GET 的简单窗体,这种方法就特别有用。
尤其是,此模板不包含通常与 MVC 项目相关联的两个文件夹:“控制器”和“视图”。Razor 页面使用“页面”文件夹保留应用程序的所有页面。可以在“页面”根文件夹内随意新建文件夹,以适合应用程序的任意方式整理页面。
借助 Razor 页面,开发者可以使用旨在生成优质代码的 MVC 模式,同时还能将往往会一起变化的对象归入一组,从而提高工作效率。
请注意,在版本 2 中,Core MVC 内置有 Razor 页面这一功能。只需添加“页面”文件夹,并将 Razor 页面文件添加到此文件夹,即可让任意 ASP.NET Core MVC 应用程序开始支持 Razor 页面。
Razor 页面,由请求时需要遵循特定的文件夹结构约定。典型 MVC 应用程序中的默认页面可以通过“/”、“/Home/”和“/Home/Index”找到,而使用 Razor 页面的应用程序中的默认索引页则是与“/”和“/Index”匹配。
使用子文件夹,可以非常直观地创建应用程序的不同部分,只需相应地匹配由即可。每个文件夹都有一个 Index.cshtml 文件,可用作自己的根页面。
查看各个页面时,大家会发现新的页面指令 @page,这是 Razor 页面必须使用的。此指令必须出现在应使用 .cshtml 扩展名的页面文件的第一行。Razor 页面的外观和行为均与基于 Razor 的视图文件非常类似,非常简单的页面可以只包括 HTML:
Razor 页面出类拔萃的地方在于,可以封装和分组 UI 详细信息。Razor 页面支持内联的或的基于类的页面模型,可以表示页面将显示或控制的数据元素。此外,还支持处理程序,这样就无需单独使用控制器和操作方法了。
这些功能大大减少了处理 Web 应用程序的给定页面需要单独使用的文件夹和文件数量。图 3 比较了基于 MVC 的典型方法与 Razor 页面方法所需的文件夹和文件。
为了模拟有点儿复杂又有一些不同功能区域的项目,我将重新使用我在介绍功能切片的文章中使用的示例。此示例涉及查看和管理许多不同类型的实体,包括忍者与忍者刀、海盗、植物和僵尸。
假设应用程序是休闲游戏伴侣,有助于管理游戏内构造。使用典型的 MVC 组织方法,最有可能会拥有许多不同的文件夹,用于保留控制器、视图、视图模型等其他所有构造。
在此示例中,应用程序有一个简单主页和四个不同部分,每个部分都在“页面”下有自己的子文件夹。文件夹结构非常清晰,“页面”文件夹根目录下直接就是主页 (Index.cshtml) 和一些支持文件,其他各部分分别位于自己的文件夹中,如图 4 所示。
在 Razor 块中声明的变量可以在整个页面上使用;下一部分将介绍如何通过 @functions 块声明函数和类。请注意,页面底部链接中使用了新的 asp-page 标记帮助程序。这些标记帮助程序按由引用页面,支持绝对径和相对径。
在此示例中,“/Ninjas/Index”也可以编写成“../Index”,甚至可以直接编写成“..”,它将由到“忍者”文件夹中的同一 Index.cshtml Razor 页面。
还可以对 form 元素使用 asp-page 标记帮助程序,从而指定窗体目标。由于 asp-page 标记帮助程序是在功能强大的Core 由支持基础之上构建而成,因此除了简单的相对 URL 之外,它们还支持许多 URL 生成方案。
Razor 页面可以支持强类型页面模型。可以使用 @model 指令,为 Razor 页面指定模型(正如强类型 MVC 视图一样)。可以在 Razor 页面文件内定义模型,如图 6 所示。
也可以在单独的 codebehind 文件 Pagename.cshtml.cs 中定义页面模型。在 Visual Studio 中,遵循此约定的文件与其对应的页面文件相关联,这样就可以在两者之间轻松进行。可以将图 6 中的 @functions 块代码置于单独的文件中。
两种用于存储页面模型的方法都各有利弊。如果将页面模型逻辑置于 Razor 页面本身内,不仅生成的文件较少,而且可以灵活运用运行时编译,这样无需完整部署应用程序,即可更新页面逻辑。
缺点是,在运行时之前,可能无法发现在 Razor 页面中定义的页面模型存在的编译错误。Visual Studio 会在打开的 Razor 文件中显示错误(而不真正进行编译)。运行 dotnet build 命令既不会编译 Razor 页面,也不会在这些文件中提供潜在错误的任何相关信息。
各个页面模型类在实现关注点分离方面稍好一些,因为 Razor 页面可以完全侧重于数据显示模板,让单独的页面模型负责处理页面数据结构和相应的处理程序。
各个 codebehind 页面模型也受益于编译时错误检查,比内联页面模型更易于进行单元测试。最后,可以选择在 Razor 页面中不使用模型、使用内联模型,还是使用单独的页面模型。
Controller 类中通常都会用到的两项关键 MVC 功能是由和模型绑定。大多数 ASP.NET Core MVC 应用程序都使用属性来定义由、Http 谓词和由参数,具体语法如下所示:
如前所述,Razor 页面的由径需要遵循约定,并与 /Pages 文件夹层次结构中的页面相匹配。不过,可以将由参数添加到 @page 指令中,从而支持由参数。Razor 页面使用遵循 OnVerb 命名约定的处理程序(其中 Verb 是 Get、Post 等 Http 谓词),而不是使用属性指定支持的 Http 谓词。
Razor 页面处理程序的行为与 MVC 控制器操作非常相似,均使用模型绑定来填充所定义的任何参数。图 7 展示的示例 Razor 页面使用由参数、依赖关系注入和处理程序来显示记录的详细信息。
页面可以支持多个处理程序,因此能够定义 OnGet、OnPost 等。Razor 页面还引入了新的模型绑定属性 [BindProperty],这对窗体特别有用。可以将此属性应用于 Razor 页面(无论是否使用显式 PageModel)上的属性,从而对向页面发出的非 GET 请求使用数据绑定。
这样一来,标记帮助程序(如 asp-for 和 asp-validation-for)就能够与指定的属性配合使用,并允许处理程序与绑定属性配合使用,而无需将属性指定为方法参数。[BindProperty] 属性也适用于 Controller。
在页面中使用同一 Http 谓词支持多个操作的情况十分常见。例如,示例中的主页面支持列出实体(以默认 GET 行为的形式),并支持删除条目或添加新条目(以 POST 请求的形式)。
Razor 页面通过使用已命名的处理程序支持此方案(如图 9 所示),即在谓词后和“Async”后缀(若有)前添加名称。PageModel 基类型与 Controller 基类型类似,都可以提供许多用于返回操作结果的帮助程序方法。如果执行更新(如添加新记录),经常需要在操作成功后立即重定向用户。
这样,就不会发生浏览器刷新触发重复调用服务器的问题,进而也就不会导致重复记录(或更糟的情况)发生。可以使用不含参数的 RedirectToPage,重定向到当前 Razor 页面的默认 GET 处理程序。
可以使用 asp-page-handler 标记帮助程序,指定已命名的处理程序,应用于窗体、链接或按钮:
asp-page-handler 标记使用由生成 URL。默认情况下,处理程序名称和任何 asp-route-parameter 属性都可以应用为查询字符串值。上一代码中的“删除”按钮生成如下 URL:
若要在单独的文件中使用页面模型,可以对 Razor 页面使用基于属性的筛选器,包括在页面模型类中添加筛选器属性。
此外,为应用程序配置 MVC 时,仍可以指定全局筛选器。筛选器的最常见用途之一是,在应用程序中指定授权策略。可以全局配置基于文件夹和页面的授权策略:
使用此筛选器,可以添加在下列时间点运行的代码:在选择特定页面处理程序后,或在处理程序方法执行前后。第一种方法可用于更改处理请求的处理程序,例如:
选择处理程序后,便会进行模型绑定。模型绑定完成后,将调用任意页面筛选器的 OnPageHandlerExecuting 方法。
此方法可以访问并控制处理程序可用的任何模型绑定数据,并快捷调用处理程序。然后,在处理程序执行后,但在操作结果执行前,调用 OnPageHandlerExecuted 方法。
请注意,Razor 页面根本不需要筛选器 ValidateAntiforgeryToken。此筛选器用于抵御跨网站请求伪造(CSRF 或 XSRF),但 Razor 页面自动内置有这种。
不同之处在于,Web Pages 主要面向新手 Web 开发者(大多数经验丰富的开发者并不太感兴趣),而 Razor 页面则将强大的体系结构设计与可接近性融为一体。
从体系结构上讲,Razor 页面并不遵循模型-视图-控制器 (MVC) 模式,因为缺少控制器。相反,Razor 页面更大程度上遵循的是许多原生应用程序开发者都应熟悉的模型-视图-视图模型 (MVVM) 模式。还可以将 Razor 页面视为页面控制器模式示例。
对此,Martin Fowler 的描述为“在网站上处理有关特定页面或操作的请求的对象。此[对象]可以是页面本身,也可以是对应于相应页面的对象。”
当然,任何使用过 ASP.NET Web 窗体的人也应该熟悉页面控制器模式,因为这也是原始 ASP.NET 页面的工作方式。
与 ASP.NET Web 窗体不同,Razor 页面是在 ASP.NET Core 基础之上构建而成,支持松散耦合、关注点分离和 SOLID 原则。Razor 页面易于进行单元测试(如果单独使用 PageModel 类的话),并为生成可的干净企业应用程序提供基础。
不要只将编写的 Razor 页面用作专为编程爱好者提供的“辅助”功能。请慎重对待 Razor 页面,并考虑 Razor 页面(单独使用或与传统的控制器和视图页面结合使用)能否减少在开发特定功能时需要跳转的文件夹数量,从而改进 ASP.NET Core 应用程序的设计。
虽然 Razor 页面不遵循 MVC 模式,但与现有 ASP.NET Core MVC 控制器和视图高度兼容,因此相互之间的切换通常都十分容易。若要将现有的控制器/视图页面迁移为使用 Razor 页面,请按照以下步骤操作:
结构完善的 MVC 应用程序通常都会包含视图、控制器、视图模型和绑定模型的文件,通常每个文件都位于项目中的不同文件夹内。使用 Razor 页面,可以将这些概念整合到一个文件夹内的多个关联文件中,同时代码还能实现逻辑关注点分离。
大多数情况下,都应该可以逆序执行这些步骤,从 Razor 页面实现迁移到基于控制器/视图的方法。可以对大多数基于 MVC 的简单操作和视图执行这些步骤。更复杂的应用程序可能需要执行其他步骤和故障排除。
示例包括四种版本的 NinjaPiratePlantZombie 组织应用程序,支持添加和查看各种类型数据。此示例展示了如何使用传统 MVC、包含区域的 MVC、包含功能切片的 MVC 和 Razor 页面,组织具有多个不同功能区域的应用程序。
请探究这些不同的方法,看看哪些方法最适合自己的 ASP.NET Core 应用程序。有关此示例的更新后源代码,请访问bit.ly/2eJ01cS。
衷心感谢以下 Microsoft 技术专家对本文的审阅:Ryan Nowak返回搜狐,查看更多
网友评论 ()条 查看