使用Directory.Build.props实现集中式程序集版本控制的问题
我最近在尝试给解决方案做部署构建时的自动统一版本控制,思路是用Deploy配置区分部署构建和内部测试构建,结果遇到了一个奇怪的问题——明明看起来配置都更新了,但最终编译的程序集还是旧版本号。先说说我做了哪些操作,再讲讲问题和解决办法:
我的操作步骤
1. 区分构建配置
基于Release创建了Deploy解决方案配置,只有用这个配置构建时,才会给所有程序集统一设置FileVersion和AssemblyVersion。
2. 统一程序集基础配置
创建了Default.targets文件,定义所有项目通用的程序集信息,所有项目都导入这个文件:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <GenerateAssemblyInfo>true</GenerateAssemblyInfo> <AssemblyTitle>$(ProjectName)</AssemblyTitle> <Company>Company Name</Company> <Copyright>Copyright</Copyright> <Product>$(ProjectName)</Product> <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> <GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> </PropertyGroup> </Project>
3. 集中管理版本号
在解决方案根目录创建Directory.Build.props,初始版本号如下:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <FileVersion>25.7.16.17977</FileVersion> <AssemblyVersion>2025.7.3.0</AssemblyVersion> </PropertyGroup> </Project>
4. 动态更新版本号
在构建序列的第一个项目里,加了一个UpdateVersion目标,用自定义工具修改Directory.Build.props的版本号,这个目标只在Deploy配置下执行,且在PreBuildEvent之前运行:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <Configurations>Debug;Release;Deploy</Configurations> <Platforms>AnyCPU</Platforms> <TargetFramework>net462</TargetFramework> <OutputType>Library</OutputType> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Deploy|AnyCPU'"> <OutputPath>$(SolutionDir)Deploy\</OutputPath> <DebugType>none</DebugType> <Optimize>true</Optimize> </PropertyGroup> <ItemGroup> <Reference Include="System.Data.DataSetExtensions" /> <Reference Include="Microsoft.CSharp" /> </ItemGroup> <ItemGroup> <Folder Include="Properties\" /> </ItemGroup> <Import Project="$(SolutionDir)Default.targets" /> <Target Name="UpdateVersion" BeforeTargets="PreBuildEvent" Condition="'$(Configuration)' == 'Deploy'"> <Message Importance="High" Text="Update assembly version ..." /> <Exec Command=""$(SolutionDir)UpdateAssemblyVersion\UpdateAssemblyVersion.exe"" EchoOff="false" /> </Target> </Project>
遇到的问题
构建时,UpdateVersion确实成功把Directory.Build.props的版本号更新成了新值(比如FileVersion:25.7.16.19196、AssemblyVersion:2025.7.4.0),而且项目obj\Deploy目录下自动生成的AssemblyInfo.cs里也显示的是新版本号:
//------------------------------------------------------------------------------ // <auto-generated> // Dieser Code wurde von einem Tool generiert. // Laufzeitversion:4.0.30319.42000 // // Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn // der Code erneut generiert wird. // </auto-generated> //------------------------------------------------------------------------------ using System; using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("Company Name")] [assembly: System.Reflection.AssemblyCopyrightAttribute("Copyright")] [assembly: System.Reflection.AssemblyFileVersionAttribute("25.7.16.19196")] [assembly: System.Reflection.AssemblyProductAttribute("MyLibrary")] [assembly: System.Reflection.AssemblyTitleAttribute("MyLibrary")] [assembly: System.Reflection.AssemblyVersionAttribute("2025.7.4.0")] // Von der MSBuild WriteCodeFragment-Klasse generiert.
但最终编译出来的程序集,版本号还是旧的初始值,完全没用到新的版本号。
问题原因
这其实是踩了MSBuild的一个常见坑:Directory.Build.props是在解决方案刚加载的时候就被读取并解析的,这个时机远早于任何项目级的目标(包括PreBuildEvent)执行。也就是说,当你的UpdateVersion目标修改Directory.Build.props时,MSBuild早就把旧版本号加载到内存里了,后续编译全程用的都是旧值——哪怕生成的AssemblyInfo.cs看起来是新的,实际编译时因为MSBuild的属性缓存机制,并没有真正生效。
解决办法
既然Directory.Build.props的加载时机太早,动态修改它在当前构建会话里没用,我们换几种思路来实现:
方案1:动态传递版本属性(推荐)
不要修改文件,直接在构建时让版本工具输出版本号,然后把版本号作为全局属性传递给所有项目:
- 先把
Directory.Build.props里的FileVersion和AssemblyVersion删掉,或者留个默认值当备选。 - 在解决方案根目录创建
Directory.Build.targets,用来统一设置版本属性:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup Condition="'$(Configuration)' == 'Deploy'"> <FileVersion>$(_DeployFileVersion)</FileVersion> <AssemblyVersion>$(_DeployAssemblyVersion)</AssemblyVersion> </PropertyGroup> </Project>
- 修改第一个项目的
UpdateVersion目标,让版本工具输出版本号,然后解析成MSBuild属性:
<Target Name="UpdateVersion" BeforeTargets="Build" Condition="'$(Configuration)' == 'Deploy'"> <Message Importance="High" Text="Update assembly version ..." /> <!-- 假设你的工具可以通过参数输出类似"FileVersion=25.7.16.19196;AssemblyVersion=2025.7.4.0"的内容到控制台 --> <Exec Command=""$(SolutionDir)UpdateAssemblyVersion\UpdateAssemblyVersion.exe" -outputVersion" EchoOff="false" ConsoleToMSBuild="true"> <Output TaskParameter="ConsoleOutput" PropertyName="_VersionOutput" /> </Exec> <!-- 解析输出的版本号 --> <PropertyGroup> <_FileVersionPart>$([System.String]::Copy('$(_VersionOutput)').Split(';')[0].Split('=')[1])</_FileVersionPart> <_AssemblyVersionPart>$([System.String]::Copy('$(_VersionOutput)').Split(';')[1].Split('=')[1])</_AssemblyVersionPart> </PropertyGroup> <!-- 设置全局属性,让所有项目都能拿到 --> <CreateProperty Value="$(_FileVersionPart)"> <Output TaskParameter="Value" PropertyName="_DeployFileVersion" /> </CreateProperty> <CreateProperty Value="$(_AssemblyVersionPart)"> <Output TaskParameter="Value" PropertyName="_DeployAssemblyVersion" /> </CreateProperty> </Target>
这样版本号会直接在构建过程中动态传递,不需要修改任何文件,也能确保所有项目用的都是最新值。
方案2:构建前先更新版本文件(适合脚本/CI场景)
如果一定要用修改Directory.Build.props的方式,那必须把版本工具的执行时机提前到MSBuild开始构建之前。比如写个PowerShell脚本:
# 先运行版本工具更新Directory.Build.props & "$PSScriptRoot\UpdateAssemblyVersion\UpdateAssemblyVersion.exe" # 再执行构建 msbuild YourSolution.sln /p:Configuration=Deploy
这样MSBuild加载Directory.Build.props时,版本号已经是新的了,自然会用新值编译。
方案3:CI/CD直接传参(最简单)
如果是在CI/CD管道里做Deploy构建,直接在调用MSBuild时传递版本号参数就行:
msbuild YourSolution.sln /p:Configuration=Deploy /p:FileVersion=25.7.16.19196 /p:AssemblyVersion=2025.7.4.0
这种方式完全不需要修改任何文件,直接覆盖所有项目的版本属性。
额外提醒
- 确保所有项目都没有手动写的
AssemblyInfo.cs里的版本属性,不然手动的属性会覆盖自动生成的。 - 你已经在
Default.targets里开了GenerateAssemblyInfo=true,这个是对的,确保MSBuild自动生成包含版本号的程序集信息。
内容来源于stack exchange

