msbuild-modernization

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

MSBuild Modernization: Legacy to SDK-style Migration

MSBuild现代化:从传统格式到SDK-style格式的迁移

Identifying Legacy vs SDK-style Projects

识别传统格式与SDK-style格式项目

Legacy indicators:
  • <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  • Explicit file lists (
    <Compile Include="..." />
    for every
    .cs
    file)
  • ToolsVersion
    attribute on
    <Project>
    element
  • packages.config
    file present
  • Properties\AssemblyInfo.cs
    with assembly-level attributes
SDK-style indicators:
  • <Project Sdk="Microsoft.NET.Sdk">
    attribute on root element
  • Minimal content — a simple project may be 10–15 lines
  • No explicit file includes (implicit globbing)
  • <PackageReference>
    items instead of
    packages.config
Quick check: if a
.csproj
is more than 50 lines for a simple class library or console app, it is likely legacy format.
xml
<!-- Legacy: ~80+ lines for a simple library -->
<?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>
  <!-- ... 60+ more lines ... -->
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
xml
<!-- SDK-style: ~8 lines for the same library -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net472</TargetFramework>
  </PropertyGroup>
</Project>
传统格式标识:
  • <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  • 显式文件列表(每个
    .cs
    文件对应一条
    <Compile Include="..." />
  • <Project>
    元素上的
    ToolsVersion
    属性
  • 存在
    packages.config
    文件
  • 包含程序集级属性的
    Properties\AssemblyInfo.cs
SDK-style格式标识:
  • 根元素上的
    <Project Sdk="Microsoft.NET.Sdk">
    属性
  • 内容极简——一个简单的项目可能只有10–15行
  • 无显式文件包含(自动隐式匹配)
  • 使用
    <PackageReference>
    项替代
    packages.config
快速判断: 如果一个简单类库或控制台应用的
.csproj
文件超过50行,则很可能是传统格式。
xml
<!-- 传统格式:简单类库约80+行 -->
<?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>
  <!-- ... 还有60+行 ... -->
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
xml
<!-- SDK-style格式:相同类库仅约8行 -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net472</TargetFramework>
  </PropertyGroup>
</Project>

Migration Checklist: Legacy → SDK-style

迁移清单:传统格式 → SDK-style格式

Step 1: Replace Project Root Element

步骤1:替换项目根元素

BEFORE:
xml
<?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')" />
  <!-- ... project content ... -->
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
AFTER:
xml
<Project Sdk="Microsoft.NET.Sdk">
  <!-- ... project content ... -->
</Project>
Remove the XML declaration,
ToolsVersion
,
xmlns
, and both
<Import>
lines. The
Sdk
attribute replaces all of them.
迁移前:
xml
<?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>
迁移后:
xml
<Project Sdk="Microsoft.NET.Sdk">
  <!-- ... 项目内容 ... -->
</Project>
移除XML声明、
ToolsVersion
xmlns
以及两个
<Import>
行。
Sdk
属性可替代所有这些内容。

Step 2: Set TargetFramework

步骤2:设置TargetFramework

BEFORE:
xml
<PropertyGroup>
  <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
</PropertyGroup>
AFTER:
xml
<PropertyGroup>
  <TargetFramework>net472</TargetFramework>
</PropertyGroup>
TFM mapping table:
Legacy
TargetFrameworkVersion
SDK-style
TargetFramework
v4.6.1
net461
v4.7.2
net472
v4.8
net48
(migrating to .NET 6)
net6.0
(migrating to .NET 8)
net8.0
迁移前:
xml
<PropertyGroup>
  <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
</PropertyGroup>
迁移后:
xml
<PropertyGroup>
  <TargetFramework>net472</TargetFramework>
</PropertyGroup>
TFM映射表:
传统格式
TargetFrameworkVersion
SDK-style格式
TargetFramework
v4.6.1
net461
v4.7.2
net472
v4.8
net48
(迁移至.NET 6)
net6.0
(迁移至.NET 8)
net8.0

Step 3: Remove Explicit File Includes

步骤3:移除显式文件包含

BEFORE:
xml
<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" />
  <!-- ... 50+ more lines ... -->
</ItemGroup>
<ItemGroup>
  <Content Include="Views\Home\Index.cshtml" />
  <Content Include="Views\Shared\_Layout.cshtml" />
  <!-- ... more content files ... -->
</ItemGroup>
AFTER:
Delete all of these
<Compile>
and
<Content>
item groups entirely. SDK-style projects include them automatically via implicit globbing.
Exception: keep explicit entries only for files that need special metadata or reside outside the project directory:
xml
<ItemGroup>
  <Content Include="..\shared\config.json" Link="config.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
迁移前:
xml
<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" />
  <!-- ... 还有50+行 ... -->
</ItemGroup>
<ItemGroup>
  <Content Include="Views\Home\Index.cshtml" />
  <Content Include="Views\Shared\_Layout.cshtml" />
  <!-- ... 更多内容文件 ... -->
</ItemGroup>
迁移后:
完全删除所有这些
<Compile>
<Content>
项组。SDK-style格式项目会通过隐式自动匹配包含这些文件。
例外情况: 仅保留需要特殊元数据或位于项目目录外的文件的显式条目:
xml
<ItemGroup>
  <Content Include="..\shared\config.json" Link="config.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

Step 4: Remove AssemblyInfo.cs

步骤4:移除AssemblyInfo.cs

BEFORE (
Properties\AssemblyInfo.cs
):
csharp
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")]
AFTER (in
.csproj
):
xml
<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>
Delete
Properties\AssemblyInfo.cs
— the SDK auto-generates assembly attributes from these properties.
Alternative: if you prefer to keep
AssemblyInfo.cs
, disable auto-generation:
xml
<PropertyGroup>
  <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
迁移前
Properties\AssemblyInfo.cs
):
csharp
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
中):
xml
<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
,请禁用自动生成:
xml
<PropertyGroup>
  <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>

Step 5: Migrate packages.config → PackageReference

步骤5:从packages.config迁移至PackageReference

BEFORE (
packages.config
):
xml
<?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>
AFTER (in
.csproj
):
xml
<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>
Delete
packages.config
after migration.
Migration options:
  • Visual Studio: right-click
    packages.config
    Migrate packages.config to PackageReference
  • CLI:
    dotnet migrate-packages-config
    or manual conversion
  • Binding redirects: SDK-style projects auto-generate binding redirects — remove the
    <runtime>
    section from
    app.config
    if present
迁移前
packages.config
):
xml
<?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
中):
xml
<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-style格式项目会自动生成绑定重定向——如果存在,删除
    app.config
    中的
    <runtime>

Step 6: Remove Unnecessary Boilerplate

步骤6:移除不必要的冗余代码

Delete all of the following — the SDK provides sensible defaults:
xml
<!-- DELETE: SDK imports (replaced by Sdk attribute) -->
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" ... />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

<!-- DELETE: default Configuration/Platform (SDK provides these) -->
<PropertyGroup>
  <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
  <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
  <ProjectGuid>{...}</ProjectGuid>
  <OutputType>Library</OutputType>  <!-- keep only if not Library -->
  <AppDesignerFolder>Properties</AppDesignerFolder>
  <FileAlignment>512</FileAlignment>
  <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
  <Deterministic>true</Deterministic>
</PropertyGroup>

<!-- DELETE: standard Debug/Release configurations (SDK defaults match) -->
<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>

<!-- DELETE: framework assembly references (implicit in SDK) -->
<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>

<!-- DELETE: packages.config reference -->
<None Include="packages.config" />

<!-- DELETE: designer service entries -->
<Service Include="{508349B6-6B84-11D3-8410-00C04F8EF8E0}" />
Keep only properties that differ from SDK defaults (e.g.,
<OutputType>Exe</OutputType>
,
<RootNamespace>
if it differs from the assembly name, custom
<DefineConstants>
).
删除以下所有内容——SDK已提供合理的默认值:
xml
<!-- 删除:SDK导入(已被Sdk属性替代) -->
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" ... />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

<!-- 删除:默认的Configuration/Platform(SDK已提供) -->
<PropertyGroup>
  <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
  <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
  <ProjectGuid>{...}</ProjectGuid>
  <OutputType>Library</OutputType>  <!-- 仅当不是Library时保留 -->
  <AppDesignerFolder>Properties</AppDesignerFolder>
  <FileAlignment>512</FileAlignment>
  <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
  <Deterministic>true</Deterministic>
</PropertyGroup>

<!-- 删除:标准的Debug/Release配置(SDK默认值与之匹配) -->
<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>

<!-- 删除:框架程序集引用(SDK已隐式包含) -->
<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>

<!-- 删除:packages.config引用 -->
<None Include="packages.config" />

<!-- 删除:设计器服务条目 -->
<Service Include="{508349B6-6B84-11D3-8410-00C04F8EF8E0}" />
仅保留与SDK默认值不同的属性(例如
<OutputType>Exe</OutputType>
、与程序集名称不同的
<RootNamespace>
、自定义的
<DefineConstants>
)。

Step 7: Enable Modern Features

步骤7:启用现代特性

After migration, consider enabling modern C# features:
xml
<PropertyGroup>
  <TargetFramework>net8.0</TargetFramework>
  <Nullable>enable</Nullable>
  <ImplicitUsings>enable</ImplicitUsings>
  <LangVersion>latest</LangVersion>
</PropertyGroup>
  • <Nullable>enable</Nullable>
    — enables nullable reference type analysis
  • <ImplicitUsings>enable</ImplicitUsings>
    — auto-imports common namespaces (.NET 6+)
  • <LangVersion>latest</LangVersion>
    — uses the latest C# language version (or specify e.g.
    12.0
    )
迁移完成后,考虑启用现代C#特性:
xml
<PropertyGroup>
  <TargetFramework>net8.0</TargetFramework>
  <Nullable>enable</Nullable>
  <ImplicitUsings>enable</ImplicitUsings>
  <LangVersion>latest</LangVersion>
</PropertyGroup>
  • <Nullable>enable</Nullable>
    —— 启用可为空引用类型分析
  • <ImplicitUsings>enable</ImplicitUsings>
    —— 自动导入常用命名空间(.NET 6+)
  • <LangVersion>latest</LangVersion>
    —— 使用最新C#语言版本(或指定例如
    12.0

Complete Before/After Example

完整的迁移前后示例

BEFORE (legacy — 65 lines):
xml
<?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>
AFTER (SDK-style — 11 lines):
xml
<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>
迁移前(传统格式——65行):
xml
<?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-style格式——11行):
xml
<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>

Common Migration Issues

常见迁移问题

Embedded resources: files not in a standard location may need explicit includes:
xml
<ItemGroup>
  <EmbeddedResource Include="..\shared\Schemas\*.xsd" LinkBase="Schemas" />
</ItemGroup>
Content files with CopyToOutputDirectory: these still need explicit entries:
xml
<ItemGroup>
  <Content Include="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
  <None Include="scripts\*.sql" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
Multi-targeting: change the element name from singular to plural:
xml
<!-- Single target -->
<TargetFramework>net8.0</TargetFramework>

<!-- Multiple targets -->
<TargetFrameworks>net472;net8.0</TargetFrameworks>
WPF/WinForms projects: use the appropriate SDK or properties:
xml
<!-- Option A: WindowsDesktop SDK -->
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

<!-- Option B: properties in standard SDK (preferred for .NET 5+) -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <UseWPF>true</UseWPF>
    <!-- or -->
    <UseWindowsForms>true</UseWindowsForms>
  </PropertyGroup>
</Project>
Test projects: use the standard SDK with test framework packages:
xml
<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>
嵌入资源: 非标准位置的文件可能需要显式包含:
xml
<ItemGroup>
  <EmbeddedResource Include="..\shared\Schemas\*.xsd" LinkBase="Schemas" />
</ItemGroup>
带有CopyToOutputDirectory的内容文件: 这些仍需要显式条目:
xml
<ItemGroup>
  <Content Include="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
  <None Include="scripts\*.sql" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
多目标框架: 将元素名称从单数改为复数:
xml
<!-- 单目标框架 -->
<TargetFramework>net8.0</TargetFramework>

<!-- 多目标框架 -->
<TargetFrameworks>net472;net8.0</TargetFrameworks>
WPF/WinForms项目: 使用相应的SDK或属性:
xml
<!-- 选项A:WindowsDesktop SDK -->
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

<!-- 选项B:标准SDK中的属性(.NET 5+推荐) -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <UseWPF>true</UseWPF>
    <!-- 或 -->
    <UseWindowsForms>true</UseWindowsForms>
  </PropertyGroup>
</Project>
测试项目: 使用标准SDK并添加测试框架包:
xml
<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>

Central Package Management Migration

集中式包管理迁移

Centralizes NuGet version management across a multi-project solution. See https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management for details.
Step 1: Create
Directory.Packages.props
at the repository root with
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
and
<PackageVersion>
items for all packages.
Step 2: Remove
Version
from each project's
PackageReference
:
xml
<!-- BEFORE -->
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />

<!-- AFTER -->
<PackageReference Include="Newtonsoft.Json" />
在多项目解决方案中集中管理NuGet版本。详情请参见https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management
步骤1: 在仓库根目录创建
Directory.Packages.props
,包含
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
以及所有包的
<PackageVersion>
项。
步骤2: 移除每个项目
PackageReference
中的
Version
xml
<!-- 迁移前 -->
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />

<!-- 迁移后 -->
<PackageReference Include="Newtonsoft.Json" />

Directory.Build Consolidation

Directory.Build文件整合

Identify properties repeated across multiple
.csproj
files and move them to shared files.
Directory.Build.props
(for properties — placed at repo or src root):
xml
<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
(for targets/tasks — placed at repo or src root):
xml
<Project>
  <Target Name="PrintBuildInfo" AfterTargets="Build">
    <Message Importance="High" Text="Built $(AssemblyName) → $(TargetPath)" />
  </Target>
</Project>
Keep in individual
.csproj
files
only what is project-specific:
xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <AssemblyName>MyApp</AssemblyName>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Serilog" />
    <ProjectReference Include="..\MyLibrary\MyLibrary.csproj" />
  </ItemGroup>
</Project>
识别多个
.csproj
文件中重复的属性,并将其移至共享文件。
Directory.Build.props
(用于属性——放置在仓库或src根目录):
xml
<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根目录):
xml
<Project>
  <Target Name="PrintBuildInfo" AfterTargets="Build">
    <Message Importance="High" Text="Built $(AssemblyName) → $(TargetPath)" />
  </Target>
</Project>
仅在单个
.csproj
文件中保留
项目特定的内容:
xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <AssemblyName>MyApp</AssemblyName>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Serilog" />
    <ProjectReference Include="..\MyLibrary\MyLibrary.csproj" />
  </ItemGroup>
</Project>

Tools and Automation

工具与自动化

ToolUsage
dotnet try-convert
Automated legacy-to-SDK conversion. Install:
dotnet tool install -g try-convert
.NET Upgrade AssistantFull migration including API changes. Install:
dotnet tool install -g upgrade-assistant
Visual StudioRight-click
packages.config
Migrate packages.config to PackageReference
Manual migrationOften cleanest for simple projects — follow the checklist above
Recommended approach:
  1. Run
    try-convert
    for a first pass
  2. Review and clean up the output manually
  3. Build and fix any issues
  4. Enable modern features (nullable, implicit usings)
  5. Consolidate shared settings into
    Directory.Build.props
工具使用方式
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