关于 msbuild-modernization
用于识别旧式项目并迁移到 SDK-style MSBuild 的 .NET 技能,面向维护 .NET 项目的开发者。内容涵盖包引用和构建设置迁移,以及常见问题检查。
主要功能
- 01识别旧式与 SDK-style 项目
- 02迁移项目文件与目标框架
- 03转换 packages.config 为 PackageReference
- 04整合中央包与共享构建设置
适用场景
- 01升级旧式 .NET 项目
- 02精简重复的 MSBuild 项目配置
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dotnet/ skills --skill msbuild-modernization安装后在 Claude Code 中输入/msbuild-modernization确认已加载。
详细介绍
MSBuild 现代化:从旧式项目迁移到 SDK 样式
识别旧式项目与 SDK 样式项目
旧式项目的特征:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />- 显式列出文件(为每个
.cs文件都添加<Compile Include="..." />) <Project>元素上有ToolsVersion属性- 存在
packages.config文件 - 存在包含程序集级属性的
Properties\AssemblyInfo.cs
SDK 样式项目的特征:
- 根元素上有
<Project Sdk="Microsoft.NET.Sdk">属性 - 内容精简——简单的项目可能只有 10–15 行
- 没有显式包含文件(使用隐式通配)
- 使用
<PackageReference>项,而非packages.config
**快速检查:**对于简单的类库或控制台应用,如果 .csproj 超过 50 行,很可能是旧式格式。
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<OutputType>Library</OutputType>
<RootNamespace>MyLibrary</RootNamespace>
<AssemblyName>MyLibrary</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
</PropertyGroup>
</Project>
迁移清单:旧式项目 → SDK 样式项目
第 1 步:替换项目根元素
之前:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"
Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
之后:
<Project Sdk="Microsoft.NET.Sdk">
</Project>
删除 XML 声明、ToolsVersion、xmlns 和两行 <Import>。Sdk 属性会取代它们。
第 2 步:设置 TargetFramework
之前:
<PropertyGroup>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
</PropertyGroup>
之后:
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
</PropertyGroup>
TFM 映射表:
旧式 TargetFrameworkVersion | SDK 样式 TargetFramework |
|---|---|
v4.6.1 | net461 |
v4.7.2 | net472 |
v4.8 | net48 |
| (迁移到 .NET 6) | net6.0 |
| (迁移到 .NET 8) | net8.0 |
第 3 步:移除显式文件包含项
之前:
<ItemGroup>
<Compile Include="Controllers\HomeController.cs" />
<Compile Include="Models\User.cs" />
<Compile Include="Models\Order.cs" />
<Compile Include="Services\AuthService.cs" />
<Compile Include="Services\OrderService.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\Home\Index.cshtml" />
<Content Include="Views\Shared\_Layout.cshtml" />
</ItemGroup>
之后:
彻底删除所有这些 <Compile> 和 <Content> 项组。SDK 样式项目会通过隐式通配自动包含这些文件。
**例外:**仅对需要特殊元数据或位于项目目录之外的文件保留显式项:
<ItemGroup>
<Content Include="..\shared\config.json" Link="config.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
第 4 步:移除 AssemblyInfo.cs
之前(Properties\AssemblyInfo.cs):
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("MyLibrary")]
[assembly: AssemblyDescription("A useful library")]
[assembly: AssemblyCompany("Contoso")]
[assembly: AssemblyProduct("MyLibrary")]
[assembly: AssemblyCopyright("Copyright © Contoso 2024")]
[assembly: ComVisible(false)]
[assembly: Guid("...")]
[assembly: AssemblyVersion("1.2.0.0")]
[assembly: AssemblyFileVersion("1.2.0.0")]
之后(在 .csproj 中):
<PropertyGroup>
<AssemblyTitle>MyLibrary</AssemblyTitle>
<Description>A useful library</Description>
<Company>Contoso</Company>
<Product>MyLibrary</Product>
<Copyright>Copyright © Contoso 2024</Copyright>
<Version>1.2.0</Version>
</PropertyGroup>
删除 Properties\AssemblyInfo.cs——SDK 会根据这些属性自动生成程序集属性。
**替代方案:**如果你希望保留 AssemblyInfo.cs,请禁用自动生成:
<PropertyGroup>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
第 5 步:将 packages.config 迁移为 PackageReference
之前(packages.config):
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net472" />
<package id="Serilog" version="3.1.1" targetFramework="net472" />
<package id="Microsoft.Extensions.DependencyInjection" version="8.0.0" targetFramework="net472" />
</packages>
之后(在 .csproj 中):
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
</ItemGroup>
迁移后删除 packages.config。
迁移选项:
- **Visual Studio:**右键单击
packages.config→ 将 packages.config 迁移到 PackageReference - CLI:
dotnet migrate-packages-config或手动转换 - **绑定重定向:**SDK 样式项目会自动生成绑定重定向——如果
app.config中存在<runtime>节,请将其删除
第 6 步:移除不必要的样板配置
删除以下所有内容——SDK 提供了合理的默认值:
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" ... />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{...}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<None Include="packages.config" />
<Service Include="{508349B6-6B84-11D3-8410-00C04F8EF8E0}" />
仅保留与 SDK 默认值不同的属性(例如 <OutputType>Exe</OutputType>、与程序集名称不同的 <RootNamespace>,以及自定义 <DefineConstants>)。
第 7 步:启用现代功能
迁移后,可考虑启用现代 C# 功能:
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<Nullable>enable</Nullable>— 启用可空引用类型分析<ImplicitUsings>enable</ImplicitUsings>— 自动导入常用命名空间(.NET 6+)- 避免使用
<LangVersion>latest— 实际生效的语言版本由 SDK/编译器默认值决定,而不只是由 TFM 决定,因此在安装了不同 SDK 的计算机上,构建结果可能会在不提示的情况下发生变化。除非需要固定特定版本,否则请省略<LangVersion>。如需确保构建可复现,可在整个代码库中通过global.json固定 SDK 版本(从而间接固定默认语言版本),或在每个项目中设置明确的数字版<LangVersion>(例如<LangVersion>12</LangVersion>),以直接控制语言版本。
完整的迁移前后示例
迁移前(旧版 — 65 行):
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"
Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{12345678-1234-1234-1234-123456789ABC}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MyLibrary</RootNamespace>
<AssemblyName>MyLibrary</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup>
<Compile Include="Models\User.cs" />
<Compile Include="Models\Order.cs" />
<Compile Include="Services\UserService.cs" />
<Compile Include="Services\OrderService.cs" />
<Compile Include="Helpers\StringExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
迁移后(SDK 风格 — 11 行):
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog" Version="3.1.1" />
</ItemGroup>
</Project>
常见迁移问题
**嵌入式资源:**不在标准位置的文件可能需要显式包含:
<ItemGroup>
<EmbeddedResource Include="..\shared\Schemas\*.xsd" LinkBase="Schemas" />
</ItemGroup>
**带有 CopyToOutputDirectory 的内容文件:**仍需显式条目:
<ItemGroup>
<Content Include="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
<None Include="scripts\*.sql" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
**多目标框架:**将元素名称从单数改为复数:
<TargetFramework>net8.0</TargetFramework>
<TargetFrameworks>net472;net8.0</TargetFrameworks>
**WPF/WinForms 项目:**使用适当的 SDK 或属性:
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
</Project>
**测试项目:**使用标准 SDK 和测试框架包:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7" />
</ItemGroup>
</Project>
中央包管理迁移
在多项目解决方案中集中管理 NuGet 版本。详情请参阅 https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management。
**步骤 1:**在仓库根目录创建 Directory.Packages.props,其中包含 <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> 和所有包对应的 <PackageVersion> 项。
**步骤 2:**从每个项目的 PackageReference 中移除 Version:
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Newtonsoft.Json" />
Directory.Build 整合
找出多个 .csproj 文件中重复的属性,并将它们移至共享文件。
Directory.Build.props(用于属性 — 放在仓库或 src 根目录):
<Project>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Company>Contoso</Company>
<Copyright>Copyright © Contoso 2024</Copyright>
</PropertyGroup>
</Project>
Directory.Build.targets(用于目标/任务 — 放在仓库或 src 根目录):
<Project>
<Target Name="PrintBuildInfo" AfterTargets="Build">
<Message Importance="High" Text="Built $(AssemblyName) → $(TargetPath)" />
</Target>
</Project>
仅在单个 .csproj 文件中保留项目特有的内容:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<AssemblyName>MyApp</AssemblyName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog" />
<ProjectReference Include="..\MyLibrary\MyLibrary.csproj" />
</ItemGroup>
</Project>
工具和自动化
| 工具 | 用途 |
|---|---|
dotnet try-convert | 自动将旧版项目转换为 SDK 风格。安装:dotnet tool install -g try-convert |
| .NET Upgrade Assistant | 完整迁移,包括 API 更改。安装:dotnet tool install -g upgrade-assistant |
| Visual Studio | 右键单击 packages.config → 将 packages.config 迁移到 PackageReference |
| 手动迁移 | 对于简单项目通常最干净 — 按照上面的清单操作 |
推荐方法:
- 运行
try-convert进行初步转换 - 手动检查并清理输出
- 构建并修复任何问题
- 启用现代功能(可空性、隐式 using)
- 将共享设置整合到
Directory.Build.props中