当前位置:首页 > 技术 > 正文内容

解决 ASP.NET Web 项目在 CI/CD 环境下 MSBuild 发布丢失 Roslyn 文件的问题

访客 技术 2026年8月3日 1

问题背景

在将持续集成与持续部署(CI/CD)流程应用于基于 .NET Framework 4.8.1 的传统 ASP.NET Web 应用程序时,可能会遇到一个隐蔽的构建问题:通过 GitLab Runner 调用 MSBuild 进行发布后,最终的输出目录中缺失 bin\roslyn 文件夹。这会导致依赖 Roslyn 编译器平台的动态编译功能(如 Razor 视图动态编译)在运行时失效。本文将深入剖析该问题的成因并提供可靠的解决方案。

Roslyn 在传统 ASP.NET 项目中的集成方式

要理解为何会丢失该文件夹,首先需要明确 Roslyn 编译器在旧版 Web 项目中的存在形式与配置方式。

1. 配置文件中的编译器声明

在项目的 web.config 文件末尾,通常包含 system.codedom 节点,用于指定使用 Roslyn 提供程序来处理 C# 和 VB.NET 代码的运行时编译:

<configuration>
  <!-- 其他系统配置节点 -->
  <system.codedom>
    <compilers>
      <compiler language="c#;cs;csharp" extension=".cs" 
                type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
                warningLevel="4" compilerOptions="/langversion:default /nowarn:1659;1699;1701" />
      <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" 
                type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
                warningLevel="4" compilerOptions="/langversion:default /nowarn:41008 /define:_MYTYPE=&quot;Web&quot; /optionInfer+" />
    </compilers>
  </system.codedom>
</configuration>

2. NuGet 包依赖

项目文件(.csproj)中必须引入相关的 NuGet 包,以提供底层的编译工具链和运行时程序集:

<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <PackageReference Include="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" Version="4.1.0" />
    <PackageReference Include="Microsoft.Net.Compilers" Version="4.2.0">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
  </ItemGroup>
</Project>

这些包不仅提供了运行时的 DLL,还包含了 csc.exevbc.exe 等实际执行编译任务的工具,它们最终需要被部署到站点的 bin\roslyn 目录下。

问题排查与日志分析

为了定位文件丢失的原因,可以在 CI/CD 脚本中为 MSBuild 启用诊断级别的日志输出(添加 /v:diag 参数)。由于诊断日志体积庞大,可能需要临时调高 GitLab Runner 的日志大小限制(例如调整至 8MB 以上)。

通过分析构建日志中关于 CopyRoslynCompilerFilesToOutputDirectory 目标的执行记录,可以发现以下关键信息:

目标 "CopyRoslynCompilerFilesToOutputDirectory":
  任务 "Copy"
    参数: DestinationFolder = D:\agent\work\src\MyWebApp\bin\roslyn
    参数: SourceFiles = C:\Users\builder\.nuget\packages\microsoft.codedom.providers.dotnetcompilerplatform\4.1.0\tools\roslyn-4.1.0\csc.exe ...
    正在创建目录 "D:\agent\work\src\MyWebApp\bin\roslyn"。

日志揭示了问题的核心矛盾:

  • 路径参数未生效:尽管在调用 MSBuild 时通过 /p:WebProjectOutputDir=D:\publish\output 指定了自定义的发布目录,但 Roslyn 复制任务并没有使用这个外部传入的路径。
  • 文件被复制到了源码目录:Roslyn 工具文件确实被复制了,但目标位置是项目源码树内部的默认 bin\roslyn 目录,而不是 CI/CD 管道指定的最终发布目录。当后续阶段打包或部署发布目录时,自然会找不到这些文件。

解决方案:自定义 MSBuild 目标

既然内置的复制任务无法正确识别外部传入的 WebProjectOutputDir 参数,我们可以通过在 .csproj 文件中注入自定义的 MSBuild Target 来手动修正这一行为。

将以下代码片段添加到项目文件的末尾:

<Target Name="DeployRoslynCompilerAssets" AfterTargets="_CopyWebApplication">
  <!-- 定义需要复制的 Roslyn 工具文件集合,支持递归子目录 -->
  <ItemGroup>
    <CompilerAssets Include="$(CscToolPath)\**\*" />
  </ItemGroup>
  
  <!-- 计算目标发布目录下的 roslyn 文件夹路径 -->
  <PropertyGroup>
    <PublishRoslynPath>$(WebProjectOutputDir)\bin\roslyn</PublishRoslynPath>
  </PropertyGroup>

  <!-- 确保目标目录存在 -->
  <MakeDir Directories="$(PublishRoslynPath)" Condition="!Exists('$(PublishRoslynPath)')" />

  <!-- 执行复制操作,使用 DestinationFiles 保留目录结构,并加入重试机制 -->
  <Copy SourceFiles="@(CompilerAssets)" 
        DestinationFiles="@(CompilerAssets->'$(PublishRoslynPath)\%(RecursiveDir)%(Filename)%(Extension)')" 
        SkipUnchangedFiles="true" 
        OverwriteReadOnlyFiles="true"
        Retries="3" 
        RetryDelayMilliseconds="1000" />
</Target>

配置要点解析

  • 执行时机 (AfterTargets):将目标绑定在 _CopyWebApplication 之后执行至关重要。在这个内置目标完成后,MSBuild 已经正确解析并应用了命令行传入的 WebProjectOutputDir 参数,确保自定义任务能获取到准确的发布路径。
  • 文件映射 (DestinationFiles):使用 %(RecursiveDir) 元数据可以完美保留源文件中的子目录结构,防止嵌套文件在复制时被展平。
  • 容错处理:加入 OverwriteReadOnlyFiles 和重试参数,可以有效避免在 CI 环境中因文件只读或短暂占用导致的构建中断。
返回列表

上一篇:C语言单链表实现原理与操作详解

没有最新的文章了...

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。