mcpField
M
msbuild-modernization

msbuild-modernization

github.com/dotnet开发5.8k3.9k 次安装1 天前更新
代码仓库

关于 msbuild-modernization

用于识别旧式项目并迁移到 SDK-style MSBuild 的 .NET 技能,面向维护 .NET 项目的开发者。内容涵盖包引用和构建设置迁移,以及常见问题检查。

主要功能

  1. 01识别旧式与 SDK-style 项目
  2. 02迁移项目文件与目标框架
  3. 03转换 packages.config 为 PackageReference
  4. 04整合中央包与共享构建设置

适用场景

  1. 01升级旧式 .NET 项目
  2. 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 映射表:

旧式 TargetFrameworkVersionSDK 样式 TargetFramework
v4.6.1net461
v4.7.2net472
v4.8net48
(迁移到 .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
手动迁移对于简单项目通常最干净 — 按照上面的清单操作

推荐方法:

  1. 运行 try-convert 进行初步转换
  2. 手动检查并清理输出
  3. 构建并修复任何问题
  4. 启用现代功能(可空性、隐式 using)
  5. 将共享设置整合到 Directory.Build.props 中

评论

支持 Markdown