PAYATHON 2026

如何为不同的解决方案分别引入 Build.props?

技术老齐

可以在共用的 Directory.Build.props 中,根据 $(SolutionName) 设置属性,或按条件导入各自的 .props 文件。共享项目会按当前解决方案的构建上下文读取相应配置。

这种方式要求项目在构建时能获取解决方案上下文。直接构建 .csproj 时,应使用默认值,或通过命令行显式传入属性。

为什么两个解决方案都会受影响

MSBuild 从项目所在目录向上查找 Directory.Build.props,找到后便导入。查找路径由项目在磁盘上的位置决定,与解决方案文件的位置无关。因此,两个解决方案中的 Project2.csproj 会找到同一个 Directory.Build.props。Microsoft Learn:按目录自定义构建

MSBuild 不会根据 MySolution.props、MySolution.Debug.props 这样的文件名自动区分解决方案,导入时需要明确写出条件。

只有少量属性时,直接按名称设置

假设需要以下配置:

  • 构建 MySolution.slnx 时,UseNuGet 为 true。
  • 构建 MySolution.Debug.slnx 时,UseNuGet 为 false。
  • 单独构建项目时,默认使用 false。

在根目录的 Directory.Build.props 中写入:

<Project>
  <PropertyGroup>
    <UseNuGet Condition="'$(UseNuGet)' == ''">false</UseNuGet>
  </PropertyGroup>

  <PropertyGroup Condition="'$(SolutionName)' == 'MySolution'">
    <UseNuGet>true</UseNuGet>
  </PropertyGroup>

  <PropertyGroup Condition="'$(SolutionName)' == 'MySolution.Debug'">
    <UseNuGet>false</UseNuGet>
  </PropertyGroup>
</Project>

$(SolutionName) 是不含扩展名的解决方案名称,因此条件中分别使用 MySolution 和 MySolution.Debug。构建解决方案时,包括 SolutionName 在内的上下文属性会传给项目。MSBuild 源码:解决方案属性传递

按此配置,Project2.csproj 通过不同解决方案构建时,会获得不同的 UseNuGet 值。Project1.csproj 通过 MySolution.Debug.slnx 构建时也会获得 false。

文件名中的 .Debug 只是解决方案名称的一部分,不会自动选择项目的 Debug 配置。构建配置仍由 $(Configuration) 和解决方案中的配置映射决定。

配置较多时,分别导入 .props 文件

可以使用以下目录结构:

根目录/
├── MySolution.slnx
├── MySolution.Debug.slnx
├── Directory.Build.props
├── build/
│   ├── MySolution.props
│   └── MySolution.Debug.props
├── Project1/
│   └── Project1.csproj
└── Project2/
    └── Project2.csproj

在 Directory.Build.props 中保留默认值,并按解决方案名称导入对应文件:

<Project>
  <PropertyGroup>
    <UseNuGet Condition="'$(UseNuGet)' == ''">false</UseNuGet>
  </PropertyGroup>

  <Import
    Project="$(MSBuildThisFileDirectory)build/MySolution.props"
    Condition="'$(SolutionName)' == 'MySolution'" />

  <Import
    Project="$(MSBuildThisFileDirectory)build/MySolution.Debug.props"
    Condition="'$(SolutionName)' == 'MySolution.Debug'" />
</Project>

build/MySolution.props:

<Project>
  <PropertyGroup>
    <UseNuGet>true</UseNuGet>
  </PropertyGroup>
</Project>

build/MySolution.Debug.props:

<Project>
  <PropertyGroup>
    <UseNuGet>false</UseNuGet>
  </PropertyGroup>
</Project>

$(MSBuildThisFileDirectory) 指向当前导入文件所在的目录。用它定位 build 文件夹,可以避免相对路径随项目位置变化。

两种写法选一种即可。

单独构建项目时,显式传入属性

直接构建共享项目时,项目本身无法确定该采用哪个解决方案的配置。这个例子只有一个开关,可以直接传入:

dotnet build Project2/Project2.csproj -p:UseNuGet=true

构建整个解决方案时,也可以统一指定:

dotnet build MySolution.slnx -p:UseNuGet=true
dotnet build MySolution.Debug.slnx -p:UseNuGet=false

通过命令行 -p 传入的是全局属性,通常会传递给子项目,且优先于项目文件中的普通属性赋值;显式声明为局部属性等特殊情况除外。Microsoft Learn:MSBuild 属性

运行这些解决方案构建命令,要求所用的 .NET SDK/MSBuild 支持 .slnx 格式。

需要注意的边界

  • UseNuGet 在这里是自定义属性,只有项目、导入文件或构建目标读取它时才会影响行为。将它设为 false 不会自动关闭 NuGet 还原或移除 PackageReference。
  • Directory.Build.props 导入较早,后续的 .csproj 或其他导入文件仍可能覆盖其中的普通属性。如果属性值不符合预期,应检查后续赋值。
  • 如果项目目录下还有更近的 Directory.Build.props,默认查找会停在那里,需要由该文件显式导入上层配置。位于根目录之外的项目也需要另行安排导入。
  • 如果同一个共享项目的依赖会随属性变化,还原和构建时应使用一致的属性。并行构建两种依赖配置时,还应考虑隔离中间文件和输出目录。

Directory.Solution.props 定制的是解决方案构建层。把这些项目属性直接移入其中,普通属性赋值不会自动传递给所有项目,而且这类解决方案构建扩展不适用于 Visual Studio 内部构建。Microsoft Learn:自定义解决方案构建

备注:内容仅供参考。