☰
ASP.NET Core MVC 性能基准:RazorRendering 基准应用的结构、运行机制与压测方法
2026/10/10 20:33:34 网站建设 项目流程
  • 后端
  • Web框架

【免费下载链接】aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.

项目地址:https://gitcode.com/GitHub_Trending/as/aspnetcore
点击查看免费下载

本文以 aspnetcore 仓库中 src/Mvc/perf/benchmarkapps/RazorRendering 下的 RazorRendering 基准应用为主体,完整解读它的定位、项目接线方式、基准页面的渲染负载设计,以及如何本地构建运行、并用 BenchmarksDriver 对其发起端到端 HTTP 压测(基准访问地址为/Category/PageA)。读完后你可以把它作为一个"真实 Razor 页面渲染场景 + 可复制的压测配置"模板,用于回归验证 MVC 渲染管线改动对性能的影响。

一、RazorRendering 基准应用的定位

在仓库的 MVC 性能测试区(src/Mvc/perf/)中,benchmarkapps 目录存放的是一批可独立运行的完整 ASP.NET Core 应用,其用途在 src/Mvc/perf/benchmarkapps/README.md 中说明得很直接:

这些项目用于辅助对 MVC 做基准测试(Benchmarking)。它们让"测试本地改动"变得更简单——无需像 Benchmarks 仓库那样依赖已发布的包,而是直接在 MVC 的分支上修改代码,然后用基准驱动工具对新分支跑基准。

RazorRendering 就是这批应用之一,专门针对Razor Pages 的视图渲染路径做端到端压测。它的 Readme.md 内容极为精炼,只有一行,但这是理解整个应用的钥匙:

Url: /Category/PageA

也就是说,对该应用施压时,基准请求的目标路由是 Razor Pages 页面Pages/Category/PageA.cshtml。项目目录结构如下:

src/Mvc/perf/benchmarkapps/RazorRendering/ ├── Data/ │ ├── DataA.cs # 表格一行的数据模型(含 HtmlString 字段) │ └── DataB.cs # 表格二行的数据模型(含时间区间字段) ├── Pages/ │ ├── Category/ │ │ ├── PageA.cshtml # 基准页面(即 Readme 指定的 /Category/PageA) │ │ ├── PageA.cshtml.cs # PageA 的 PageModel 代码隐藏 │ │ └── _Subcategories.cshtml # 侧边子分类 partial │ ├── Shared/_Layout.cshtml # 页面布局(渲染 Subcategories/Tabs 区段) │ ├── Page.cs # 所有页面的基类 PageModel │ ├── _ViewImports.cshtml # 注册 Tag Helper │ └── _ViewStart.cshtml # 指定 Layout ├── RazorRendering.csproj ├── Readme.md # 即"基准 URL"说明 └── Startup.cs # 主机配置 + 基准数据生成

二、项目接线:TFM、引用方式与主机配置

2.1 项目文件:本地跑用源码工程引用

RazorRendering.csproj 基于Microsoft.NET.Sdk.Web,目标框架取自仓库统一的 MSBuild 属性:

<PropertyGroup> <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> <IsTestAssetProject>true</IsTestAssetProject> </PropertyGroup>

其中DefaultNetCoreTargetFramework在 eng/Versions.props 中被定义为net12.0。更关键的是下面这段条件引用(源码注释写明 "These references are used when running locally"):

<!-- These references are used when running locally --> <ItemGroup Condition="'$(BenchmarksTargetFramework)' == ''"> <PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" /> <ProjectReference Include="..\..\..\..\Servers\Kestrel\Kestrel\src\Microsoft.AspNetCore.Server.Kestrel.csproj" /> <ProjectReference Include="..\..\..\Mvc.Abstractions\src\Microsoft.AspNetCore.Mvc.Abstractions.csproj" /> <ProjectReference Include="..\..\..\Mvc.Core\src\Microsoft.AspNetCore.Mvc.Core.csproj" /> <ProjectReference Include="..\..\..\Mvc.Razor\src\Microsoft.AspNetCore.Mvc.Razor.csproj" /> <ProjectReference Include="..\..\..\Mvc\src\Microsoft.AspNetCore.Mvc.csproj" /> </ItemGroup>

从这段结构可以推断出该应用的双模工作方式的分工:当BenchmarksTargetFramework属性为空(即在本仓库本地构建运行时),直接以源码工程引用的形式依赖当前分支的 Kestrel 与 Mvc/Mvc.Razor 等核心项目——这正是 benchmarkapps 目录的核心价值:你在 MVC 分支上改的每一行渲染代码都会直接编译进这个被测服务。而从源码结构看,一旦基准构建流程设定了BenchmarksTargetFramework,这些工程引用即被跳过,改用发布包构建,以便对"发布产物"而非工作区状态做基准。

另外注意 benchmarkapps 下的 NuGet.config 只保留了一个包源,并配有空的 Directory.Build.props 和 Directory.Build.targets(注释说明后者用于阻止父目录的其他 targets 介入),使这批应用在与主构建隔离的、可控的环境下构建。

2.2 Startup.cs:Kestrel、路由与基准数据的注入

Startup.cs 承担三件事:生成基准数据、装配 MVC、配置路由。

基准数据:在应用启动前以静态方式生成两组各 100 条的数据集,并通过 DI 注册为 Scoped 服务:

public void ConfigureServices(IServiceCollection services) { services.AddScoped<List<DataA>>(_ => DataA); services.AddScoped<List<DataB>>(_ => DataB); services.AddMvc(); } private static List<DataA> DataA = GenerateDataA(); // DataA: Enumerable.Range(0, 100) 共 100 条, // 每条含 HtmlString 的 Icon/Html、Name、Seconds、Max、PerHour = 60f / i

数据模型定义在 Data/DataA.cs 与 Data/DataB.cs:DataA的字段是Id、HtmlString Icon、HtmlString Html、string Name、Seconds、Max、PerHour;DataB则是Id、Icon、Name、Value以及StartDate/CompleteDate两个DateTimeOffset。注意数据集中刻意混用了HtmlString(渲染时跳过编码)与普通string(渲染时执行 HTML 编码),这覆盖了 Razor 输出管线的两条编码路径。

路由与端口:

app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapDefaultControllerRoute(); endpoints.MapRazorPages(); // 基准目标 /Category/PageA 由 Razor Pages 路由命中 });

主机通过HostBuilder+ConfigureWebHost构建,UseKestrel()并固定监听http://+:5000(见 Startup.cs L70-L87),配置源为环境变量 + 命令行,因此本地dotnet run时服务固定跑在 5000 端口。

三、基准页面设计:一个有代表性的 Razor 渲染负载

RazorRendering 的价值在于其页面不是"hello world",而是模拟了一个典型的、计算与输出交织的管理后台页面。逐层看它的设计:

3.1 页面基类 Page.cs

Pages/Page.cs 是所有页面的PageModel基类,持有PageIcon、PageTitle、PageUrl三个受保护属性,以及一个演示"通过响应头回传错误"的辅助方法:

public void AddErrorMessage(string message) { Response.Headers.Add("X-Error-Message", UrlEncoder.Default.Encode(message)); }

3.2 PageA 模型:构造注入与异步缺口

Pages/Category/PageA.cshtml.cs 中,PageA : Page通过构造函数注入 DI 中的两组数据集与日志器,并暴露页面常量(Value = 0、Value3 = 1、Condition = true等)。注意OnGetAsync里有一句刻意的await Task.Delay(0):

public async Task OnGetAsync() { PageTitle = "PageA Title"; PageIcon = "sicon dialogue_pagea"; await Task.Delay(0); // 引入一次异步让出,覆盖异步请求管线 }

这让每次基准请求都会真实走过异步 handler 路径,而不是纯同步路径。

3.3 PageA.cshtml:渲染负载的构成

Pages/Category/PageA.cshtml 是压测目标,单页即覆盖了 Razor 渲染管线的多个热点:

  1. Section 与 Partial:页面声明@section Subcategories { <partial name="_Subcategories" /> }与@section Tabs { ... },由布局页渲染(见 3.4)。_Subcategoriespartial(Pages/Category/_Subcategories.cshtml)输出 7 个带data-url的子分类图标。
  2. @switch 与条件渲染:按Model.Value切换<h1>文案;表头中@if (Model.Value3 != 0)决定多一列。
  3. 大集合循环 + Tag Helper:对Model.Data1(100 条)逐行渲染表格,行内嵌<form asp-page-handler="Make">(表单 Tag Helper 解析页面 handler)、隐藏域与占位符格式化(System.Globalization.NumberFormatInfo.InvariantInfo)。
  4. 字符串格式化与时间计算:第二张表遍历Model.Data2,每行执行DateTimeOffset减法、毫秒占比换算、MathF.Min/Max钳制,并用@using static System.Convert引入的ToInt64把两个时间点换算成 Unix 秒写入data-start/data-end属性,最后输出N0/N2格式的进度与速率文本——这类"行内计算 + 格式化"正是真实后台页面里最常见的渲染开销来源。
  5. HtmlString 与 string 混排:同一条记录中@data.Icon(HtmlString,免编码)与@data.Name(string,需编码)并存,压测编码分支。

3.4 布局与视图导入

  • Pages/_ViewStart.cshtml:Layout = "_Layout",让每个页面走"布局查找 + 区段合并"流程;
  • Pages/_ViewImports.cshtml:@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers,启用整套 MVC Tag Helper 管线(表单、表单值等),这是页面内asp-page-handler生效的前提;
  • Pages/Shared/_Layout.cshtml:先@RenderSection("Subcategories"),再用IsSectionDefined("Tabs")条件渲染 Tab 区,最后@RenderBody()注入页面主体——完整覆盖了布局页对子页区段的探测与合成。

把这三层合起来看,一次对/Category/PageA的 GET 请求会依次触发:Razor Pages 路由与模型绑定 → PageModel 构造与OnGetAsync→ 页面模板编译执行(循环、switch、Tag Helper、格式化)→ partial 渲染 → 布局区段合成 → HtmlString/string 编码决策 → Kestrel 写出。这正是该基准应用想度量的一条完整渲染链路。

四、本地构建与运行

前置条件由 global.json 约束:SDK11.0.100-rc.1,且提示"未找到 SDK 时先运行./restore.cmd或./restore.sh"。在仓库根目录执行:

./restore.sh # 引导仓库所需的 .NET SDK 到 .dotnet 目录 ./activate.sh # 激活仓库本地环境(可选)

然后构建并运行基准应用(以仓库根为工作目录):

dotnet build src/Mvc/perf/benchmarkapps/RazorRendering/RazorRendering.csproj -c Release dotnet run --project src/Mvc/perf/benchmarkapps/RazorRendering/RazorRendering.csproj -c Release

服务固定监听http://+:5000,启动后用 Readme 指定的基准 URL 验证:

GET http://localhost:5000/Category/PageA

能看到包含两张 100 行数据表格的 HTML,即表示被测路径(页面 + partial + 布局 + 数据注入)工作正常,可以进行压测。

五、用 BenchmarksDriver 驱动端到端压测

src/Mvc/perf/benchmarkapps/README.md 给出了官方流程(按其步骤重述,并去除其中指向旧仓库位置的链接):

  1. 把待测改动推送到一个分支;

  2. 克隆 Benchmarks 仓库,或全局安装BenchmarksDriver工具;

  3. 如果使用克隆的仓库,进入其 BenchmarksDriver 项目;

  4. 参照如下命令形式,以你的分支配置发起基准:

    benchmarks --server <server-endpoint> --client <client-endpoint> -j <指向 benchmarks.json 的地址>

    其中-j参数指向描述"压哪些 URL、用什么客户端、请求头如何设置"的benchmarks.json配置。

当前仓库里,同级的 BasicApi、BasicViews 应用已各自带有benchmarks.json,可直接作为 RazorRendering 的编写参照。以 BasicViews/benchmarks.json 为例,其结构分两部分:Default节定义全局设定(压测客户端为Wrk、预设请求头、ReadyStateText就绪探测文本"Application started."、以及指向本仓库具体.csproj的Source工程坐标),其余每个命名场景则定义Path、Query及可选的 wrk Lua 脚本(同目录下的post.lua、postWithToken.lua即被脚本引用的压测逻辑)。BasicViews 的 HTML 场景如下:

"Default": { "Client": "Wrk", "Headers": { "Cache-Control": "no-cache" }, "PresetHeaders": "Html", "ReadyStateText": "Application started.", "Source": { "BranchOrCommit": "main", "Project": "src/Mvc/perf/benchmarkapps/BasicViews/BasicViews.csproj" } }, "BasicViews.GetTagHelpers": { "Path": "/Home/Index" }

需要注意一个现状:从仓库结构看,RazorRendering 目录下目前并没有随附benchmarks.json(该目录只有 Readme 指明 URL),因此用驱动工具跑它之前,需要参照上述 BasicViews/BasicApi 的写法,为Path: /Category/PageA补充一份配置——这也正好解释了 Readme 为什么只写一行 URL:它记录的就是这份配置里场景条目的关键输入。

六、与 Microbenchmarks 的分工对照

同一src/Mvc/perf/下还有 Microbenchmarks 目录,存放进程内的 Benchmark.NET 风格基准(如 ActionSelector、ValidationVisitor、TagBuilder 等),其 使用说明 为:以 Release 编译后dotnet run -c Release <benchmark_name>运行单项、All运行全部、不带参数则列出全部可用基准。两类设施互补:Microbenchmarks 度量单个热点方法,RazorRendering 这类 benchmarkapp 度量"一次真实 HTTP 请求穿过整个渲染栈"的端到端成本。做渲染相关改动时,先用端到端应用确认回归面,再进微基准定位具体开销,是这套目录组织给出的工作流暗示(从目录组织推断)。

七、小结与适用前提

  • RazorRendering 是 aspnetcore 仓库中面向Razor Pages 渲染管线回归的端到端压测应用,基准目标 URL 为 Readme.md 指定的/Category/PageA;
  • 它的核心工程点是 RazorRendering.csproj 中以BenchmarksTargetFramework为条件的源码工程引用,使本地运行直接测量当前分支的 MVC/Kestrel 代码;
  • 页面负载刻意覆盖 Section/Partial、Tag Helper、大循环、行内时间计算与格式化、HtmlString/string 编码分支等渲染热点,配合 Startup.cs 中 100+100 条的 DI 数据集,构成稳定的对照基准;
  • 本地运行需先./restore.sh引导 SDK(见 global.json),服务固定监听 5000 端口;对外施压建议按 benchmarkapps 父 README 的流程使用 BenchmarksDriver,并为该场景参照 BasicViews/benchmarks.json 补充配置;
  • 适用前提:目标框架为仓库定义的net12.0(net 版本随仓库主线演进而变),所有性能数值强依赖执行环境,本文仅描述结构与用法,不提供任何跨环境可比的性能结论。
  • 后端
  • Web框架

【免费下载链接】aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.

项目地址:https://gitcode.com/GitHub_Trending/as/aspnetcore
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询