
ASP.NET Core Diagnostics 中间件内嵌 Razor 视图的编译与再生成RazorPageGenerator 开发工作流全解析【免费下载链接】aspnetcoreASP.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/aspnetcoreASP.NET Core 源码仓库中的Diagnostics 中间件开发者异常页、编译错误页等在交付时不携带任何外部视图模板其 HTML 页面全部以预编译的 Razor 视图形式内嵌进程序集。本文以仓库文档 src/Middleware/Diagnostics/src/README.md 为核心脉络结合 RazorPageGenerator 工具源码 与中间件实现系统讲解这套视图的生成原理、工具命令行用法以及修改*.cshtml后必须执行的再生成流程。读完本文你将能够独立修改 Diagnostics 内嵌页面、正确触发视图重编译并从源码级理解这套写 Razor → 编译成 C# → 内嵌渲染的工程化机制。1. 关联文档说了什么一段必须执行的开发期工序在 src/Middleware/Diagnostics/src/README.md 中正文只有Development一节却点明了一个极易被贡献者忽略的关键事实Diagnostics middleware likeDeveloperExceptionPageuses compiled Razor views. After updating the*.cshtmlfile you must run the RazorPageGenerator tool to generate an updated compiled Razor view.也就是说DeveloperExceptionPage这类诊断中间件使用的不是运行时动态编译的 Razor 页面而是提前编译好的 C# 视图类视图的源代码是*.cshtml但它只是编译输入一旦修改了*.cshtml必须手动运行代码生成工具产出更新后的编译视图否则修改不会进入最终的程序集。文档给出的执行命令需在工具项目目录内运行dotnet run Microsoft.AspNetCore.Diagnostics.RazorViews path-to-aspnetcore-middleware-diagnostics-src其中第一个参数是生成类的根命名空间Microsoft.AspNetCore.Diagnostics.RazorViews第二个参数是 Diagnostics 中间件源码目录即本仓库的src/Middleware/Diagnostics/src。这段文档虽然简短但它背后是一个完整的预编译 Razor 视图工程体系。下面我们沿着仓库源码把它彻底讲透。2. 为什么诊断页面要做成预编译视图2.1 运行时不依赖任何视图引擎在 ASP.NET Core 常规的 MVC/Razor Pages 应用中.cshtml通常在运行时由 Razor 引擎编译或由 SDK 在构建期预编译。而 Diagnostics 中间件是一个自包含的基础设施组件它需要在异常发生时、甚至在应用自身已处于故障状态时仍能稳定地输出 HTML 错误页。从 Microsoft.AspNetCore.Diagnostics.csproj 可以看到该程序集的描述ASP.NET Core middleware for exception handling, exception display pages, and diagnostics information. Includes developer exception page middleware, exception handler middleware, runtime info middleware, status code page middleware, and welcome page middleware它把异常处理、错误展示、状态码页、欢迎页等能力全部打包进一个不引用 Razor 运行时引擎的程序集。视图模板在开发期被编译成普通 C# 类后随程序集分发渲染时仅需new一个视图对象、调用ExecuteAsync写出 HTML无需加载任何cshtml文件或触发运行时编译——这正是错误场景下可靠性所要求的。从该 csproj 中还能看到两个佐证IsAspNetCoreApptrue/IsAspNetCoreApp IsTrimmabletrue/IsTrimmable作为Microsoft.AspNetCore.App共享框架的一部分且可裁剪它不可能在运行时携带解析 Razor 语法所需的整套引擎与文件系统资源把视图烧进程序集是最务实的选择。2.2 共享的 Razor 视图基础设施编译后的视图类都继承自同一套抽象基类。csproj 中有如下引用Compile Include$(SharedSourceRoot)RazorViews\*.cs /该共享目录 src/Shared/RazorViews/BaseView.cs 定义了抽象基类Microsoft.Extensions.RazorViews.BaseView它是所有生成视图的运行基座核心成员包括Context、Request、Response当前HttpContext及 HTTP 对象供视图内部直接读写如Response.ContentType、Response.StatusCodeOutput写入缓冲区的TextWriterHtmlEncoder/UrlEncoder/JavaScriptEncoder视图内编码 HTML、URL、脚本内容所用ExecuteAsync(Stream)与ExecuteAsync(HttpContext)先向内存缓冲写入内容、整体编码为 UTF-8无 BOM后再拷贝到目标流/响应体。同目录的 AttributeValue.cs、HelperResult.cs 则提供属性值与辅助片段的承载类型。可以说这套共享代码定义了生成视图与运行环境之间的全部契约。3. 仓库里的视图资产布局Diagnostics 的视图散落在中间件子目录下的Views文件夹中遵循源cshtml 生成Designer.cs成对存放的约定src/Middleware/Diagnostics/src/ ├── DeveloperExceptionPage/ │ ├── Views/ │ │ ├── ErrorPage.cshtml # 运行时异常页模板源 │ │ ├── ErrorPage.Designer.cs # 由模板生成的编译视图类 │ │ ├── ErrorPage.css # 页面样式 │ │ ├── ErrorPage.js # 页面前端交互 │ │ ├── ErrorPageModel.cs # 视图模型页面数据对象 │ │ ├── CompilationErrorPage.cshtml # 编译错误页模板源 │ │ ├── CompilationErrorPage.Designer.cs │ │ └── CompilationErrorPageModel.cs │ ├── DeveloperExceptionPageMiddlewareImpl.cs │ ├── DeveloperExceptionPageExtensions.cs │ └── DeveloperExceptionPageOptions.cs ├── ExceptionHandler/ # IExceptionHandler 异常处理器 ├── StatusCodePage/ # 状态码页中间件 └── WelcomePage/ └── Views/ ├── WelcomePage.cshtml # 欢迎页模板源 └── WelcomePage.Designer.cs值得注意的规律是每个*.cshtml的旁边必定存在同名.Designer.cs且.Designer.cs是携带// auto-generated/标记的机器产物——这与 README 描述的更新 cshtml 后必须再生成的工序一一对应。仓库中提交的.Designer.cs就是工具上一次运行的结果任何人对cshtml的修改最终都要落到这些生成文件上才会生效。3.1 模板如何引用静态资源%$ include: %内联机制打开 ErrorPage.cshtml 会发现它没有以link、script src的方式引用 CSS/JS而是style %$ include: ErrorPage.css % /style ... script //!-- %$ include: ErrorPage.js % //-- /script%$ include: 文件名 %是 RazorPageGenerator 识别的一种编译期内联指令生成器在编译前读取指令指向的同目录文件把其文本直接嵌入模板内容。这样样式与脚本会随视图一起被编译进 C# 字符串字面量最终随程序集整体发布页面零外部依赖、天然具备离线可用性。关于该指令的处理细节见下文工具源码剖析Program.cs 中的ProcessFileIncludes。4. 生成产物的形态从 cshtml 到 C# 类以编译错误页为例对比源与产物的对应关系最能直观理解这套机制。CompilationErrorPage.cshtml 源模板顶部有一段页面级逻辑{ Response.StatusCode 500; Response.ContentType text/html; charsetutf-8; Response.ContentLength null; // Clear any prior Content-Length }而生成的 CompilationErrorPage.Designer.cs 会把这套内容翻译成强类型的视图类其骨架为// auto-generated/ #pragma warning disable 1591 namespace Microsoft.AspNetCore.Diagnostics.RazorViews { internal class CompilationErrorPage : Microsoft.Extensions.RazorViews.BaseView { public async override global::System.Threading.Tasks.Task ExecuteAsync() { Response.StatusCode 500; Response.ContentType text/html; charsetutf-8; Response.ContentLength null; // Clear any prior Content-Length WriteLiteral(!DOCTYPE html\r\nhtml\r\n...); // HTML 静态片段 Write(Resources.ErrorPageHtml_Title); // 表达式/资源 WriteLiteral(...); // 继续输出 } public CompilationErrorPage(CompilationErrorPageModel model) { Model model; } public CompilationErrorPageModel Model { get; set; } } }几个关键形态特征类名取自文件名ErrorPage.cshtml→internal class ErrorPageCompilationErrorPage.cshtml→internal class CompilationErrorPage全部生成在命名空间Microsoft.AspNetCore.Diagnostics.RazorViews下基类固定均继承Microsoft.Extensions.RazorViews.BaseView见 BaseView.cs模板代码被切分为WriteLiteral静态 HTML与Write动态表达式/资源字符串调用序列Razor 的控制流foreach、if被保留为原生 C# 语句构造函数接收模型对象如CompilationErrorPageModel视图把渲染所需的数据以强类型字段暴露。对比 ErrorPage.cshtml 及其对应模型 ErrorPageModel.cs 可以确认模型承载的是渲染所需的数据集错误明细、栈帧、Query/Cookie/Header、端点与路由信息视图则负责把这些数据排版成 HTML。5. 中间件运行时如何消费这些编译视图预编译视图不是摆设它们在中间件处理链路的关键节点被实例化并执行。以 DeveloperExceptionPageMiddlewareImpl.cs 为例它可以同时处理两类异常编译错误如 Razor 页面编译失败——构造CompilationErrorPage并执行var model new CompilationErrorPageModel(_options); var errorPage new CompilationErrorPage(model); if (compilationException.CompilationFailures null) { return errorPage.ExecuteAsync(context); }运行时异常——从ExceptionDetailsProvider取详情、填充模型后渲染ErrorPagevar model new ErrorPageModel { Options _options, ErrorDetails _exceptionDetailsProvider.GetDetails(ex), ... Title title, }; var errorPage new ErrorPage(model); return errorPage.ExecuteAsync(context);可以看到中间件里根本不存在视图查找/模板引擎这一步new ErrorPage(model)之后直接ExecuteAsync(context)把 HTML 写入响应——这正是 2.1 节所述的自包含、高可靠设计落地后的样子。同理WelcomePage目录下的 WelcomePage.cshtml 与其生成类也由欢迎页中间件 WelcomePageMiddleware.cs 以相同方式渲染。此外csproj 中InternalsVisibleTo声明了对Microsoft.AspNetCore.Diagnostics.Tests的可见性也说明这些视图类连同中间件实现是被仓库内部测试直接覆盖验证的此处不再展开其测试细节。6. 工具本体RazorPageGenerator 源码剖析README 提到的生成工具位于仓库 src/Middleware/tools/RazorPageGenerator这是一个仅供内部使用的控制台程序其 csproj 描述为 Builds Razor pages for views in a project. For internal use only.AssemblyName为dotnet-razorpagegeneratorIsShipping为false。其全部逻辑集中在 Program.cs下面按流程拆解。6.1 命令行参数与入口校验Program.Main对参数做了严格校验参数不足时输出内嵌用法说明并返回退出码 1dotnet razorpagegenerator root-namespace-of-views [directory path [#line path prefix]]三个参数的含义root-namespace-of-views必填生成视图类的根命名空间对 Diagnostics 即Microsoft.AspNetCore.Diagnostics.RazorViewsdirectory path可选默认当前目录递归扫描哪个项目目录下的Views子文件夹#line path prefix可选生成文件中#line指令使用的路径前缀用于屏蔽开发者本机绝对路径。扫描、生成与写盘过程由MainCore完成最后打印 N files successfully generated.。6.2 编译引擎的定制CreateProjectEngine这是整个工具的核心。它基于Microsoft.AspNetCore.Razor.Language的RazorProjectEngine构建了一个高度定制化的编译环境RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem, builder { builder .SetNamespace(rootNamespace) // 生成类命名空间 .SetBaseType(Microsoft.Extensions.RazorViews.BaseView) // 固定基类 .ConfigureClass((document, class) { class.ClassName Path.GetFileNameWithoutExtension(document.Source.FilePath); class.Modifiers.Clear(); class.Modifiers.Add(internal); // 强制 internal }); SectionDirective.Register(builder); // 支持 section builder.Features.Add(new SuppressChecksumOptionsFeature()); // 去掉 checksum builder.Features.Add(new SuppressMetadataAttributesFeature()); // 去掉元数据特性 builder.AddDefaultImports( using System using System.Threading.Tasks ); });对照 4 节看到的产物特征可以一一印证SetBaseType(Microsoft.Extensions.RazorViews.BaseView)对应生成类统一的基类类名 文件名不含扩展名、一律internal对应 Designer 文件中的internal class XxxPageSuppressChecksumOptionsFeature关闭生成代码中的源校验和checksumSuppressMetadataAttributesFeature不生成 Razor 元数据特性两者共同保证每次生成的.Designer.cs内容稳定、可 diff——这对象ErrorPage.cshtml/CompilationErrorPage.cshtml这类需入库、需被评审的生成文件至关重要默认导入System、System.Threading.Tasks保证生成类无需逐文件重复using。6.3 目录扫描与产物落盘MainCorevar viewDirectories Directory.EnumerateDirectories(targetProjectDirectory, Views, SearchOption.AllDirectories);工具会递归搜索目标目录下所有名为Views的文件夹对其中每个.cshtml/.razor项目项调用GenerateCodeFile。每个文件的处理逻辑为var projectItemWrapper new FileSystemRazorProjectItemWrapper(projectItem, physicalPathPrefix); var codeDocument projectEngine.Process(projectItemWrapper); var cSharpDocument codeDocument.GetCSharpDocument(); ... var generatedCodeFilePath Path.ChangeExtension(projectItem.PhysicalPath, .Designer.cs);要点输出文件路径通过Path.ChangeExtension(..., .Designer.cs)得到——这正是第 3 节观察到的cshtml 旁必然躺着一个同名.Designer.cs的成因即使 Razor 语法诊断存在错误工具也不会中断而是打印 One or more parse errors encountered... 后继续便于一次性暴露所有视图的问题逐个视图在控制台输出 Generating code file for view X... Done!便于人工核对产物。6.4 路径遮蔽与文件内联FileSystemRazorProjectItemWrapperGenerateCodeFile并不是直接处理源文件而是包了一层FileSystemRazorProjectItemWrapper它承担两项职责一是路径遮蔽防止开发者本机绝对路径泄漏进提交物// Mask the full name since we dont want a developers local file paths to be committed. PhysicalPath ${physicalPathPrefix}{_source.FileName};即生成代码中的物理路径只保留前缀 文件名若指定#line path prefix则为前缀形式保证仓库内生成文件的#line指向是仓库内统一可见的相对形式。二是内联指令展开对应第 3.1 节的include语法var startMatch %$ include: ; var endMatch %; ... var includeFileName cshtmlContent.Substring(...); var includeFileContent File.ReadAllText(Path.Combine(basePath, includeFileName)); cshtmlContent string.Concat(前段, includeFileContent, 后段);它会循环扫描模板内容把每一处%$ include: ErrorPage.css %形式的指令替换为该文件的实际内容例如把 ErrorPage.css 与 ErrorPage.js 直接嵌进 ErrorPage.cshtml随后才把拼接结果交给 Razor 引擎编译。这样静态资源与标记模板在编译期就合为一体。7. 动手实践修改视图后的完整再生成流程回到 README 的指引把整套流程落到实处。7.1 触发再生成的场景只要涉及以下任一视图源的改动都必须重新运行生成器ErrorPage.cshtml开发者异常页栈帧展示、Query/Cookie/Header/路由页签等CompilationErrorPage.cshtml编译错误页WelcomePage.cshtml欢迎页被上述模板内联引用的静态资产如 ErrorPage.css、ErrorPage.js它们的内容经由 include 指令被烘焙进生成的.Designer.cs。注意仅修改.cs如模型ErrorPageModel.cs不需要此流程只有模板类内容发生变化才需要再生成。7.2 执行命令依据 README先在工具项目目录中执行Windows 路径写法参考cd src\Middleware\tools\RazorPageGenerator dotnet run Microsoft.AspNetCore.Diagnostics.RazorViews path-to-aspnetcore-middleware-diagnostics-src其中path-to-aspnetcore-middleware-diagnostics-src应替换为本仓库的 Diagnostics 源码目录即src/Middleware/Diagnostics/src实际执行时请替换为绝对路径或相对于工具目录的路径。若希望在生成文件的#line指令中统一使用相对路径前缀可追加第三个参数对应 Program.cs 帮助文本里的 #line path prefix例如dotnet run Microsoft.AspNetCore.Diagnostics.RazorViews diagnostics-src ../Views/7.3 运行后应检查什么运行结束时控制台会输出生成的视图文件数量N files successfully generated.。随后建议核对对应.Designer.cs的时间戳与内容是否更新例如修改ErrorPage.cshtml后ErrorPage.Designer.cs 中的WriteLiteral/Write序列应与模板的新内容一致.Designer.cs是入库文件由于它携带// auto-generated/标记且会被提交请在提交时将源模板与生成的 Designer 一并包含保证仓库内二者始终同步这正是不再生成就会改了不生效的根因不引入本机绝对路径生成文件中不应出现开发者本机的完整目录路径只应有文件名或指定的相对前缀文件编码与可裁剪性生成类依赖 BaseView.cs 提供的无 BOM UTF-8 写入与缓冲逻辑无需手工干预。7.4 常见误区误以为修改 cshtml 后运行dotnet build即可自动生效Diagnostics 程序集不引用 Razor 编译目标构建过程不会把.cshtml编译为视图必须显式运行 RazorPageGenerator 更新.Designer.cs改动才会随程序集生效误以为生成工具属于对外 SDK 的一部分该工具 csproj 中IsShippingfalse、ExcludeFromSourceOnlyBuildtrue定位是仓库内部开发工具使用它的正确姿势是克隆本仓库后在src/Middleware/tools/RazorPageGenerator目录内按上述命令运行而非法包后依赖 NuGet 引入。8. 一条可复现的端到端对照链把全文串起来一条模板 → 生成 → 渲染的完整链路是开发者编辑 ErrorPage.cshtml含%$ include: %内联资源运行dotnet run Microsoft.AspNetCore.Diagnostics.RazorViews diagnostics-srcProgram.cs 通过CreateProjectEngine按BaseView基类 固定命名空间 internal规则生成代码FileSystemRazorProjectItemWrapper负责路径遮蔽与 include 展开最终在 ErrorPage.Designer.cs 落盘应用运行期抛异常时DeveloperExceptionPageMiddlewareImpl.cs 组装ErrorPageModel并new ErrorPage(model)视图类基于 BaseView.cs 的ExecuteAsync把编译好的 HTML 写入响应浏览器呈现出带栈帧、请求头、Cookie、路由等页签的诊断页面。这一链路解释了 README 那句要求的全部动机模板不是运行时资产而是编译期输入只有理解了它才能保证每次修改cshtml后都记得再生成.Designer.cs让开发者异常页始终反映你的最新改动。【免费下载链接】aspnetcoreASP.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),仅供参考