.NET项目结构和构建配置Skill dotnet-project-structure

.NET 项目结构和构建配置技能涉及设置新 .NET 解决方案,采用现代最佳实践,包括集中式构建属性、集中包版本管理、SourceLink 调试、自动化版本管理和 SDK 版本固定。

后端开发 0 次安装 0 次浏览 更新于 2/26/2026

.NET 项目结构和构建配置

何时使用此技能

使用此技能时:

  • 设置新的 .NET 解决方案,采用现代最佳实践
  • 跨多个项目配置集中式构建属性
  • 实施集中式包版本管理
  • 设置 SourceLink 以便于调试和 NuGet 包
  • 自动化版本管理与发布说明
  • 固定 SDK 版本以实现一致构建

相关技能

  • dotnet-local-tools - 使用 dotnet-tools.json 管理本地 .NET 工具
  • microsoft-extensions-configuration - 配置验证模式

解决方案文件格式 (.slnx)

.slnx 格式是 .NET 9 引入的现代基于 XML 的解决方案文件格式。它取代了传统的 .sln 格式。

与传统 .sln 的优势对比

方面 .sln(传统) .slnx(现代)
格式 自定义文本格式 标准 XML
可读性 GUIDs,晦涩的语法 清晰,人类可读
版本控制 难以 diff/merge 易于 diff/merge
编辑 需要 IDE 任何文本编辑器

版本要求

工具 最低版本
.NET SDK 9.0.200
Visual Studio 17.13
MSBuild Visual Studio Build Tools 17.13

注意: 从 .NET 10 开始,默认创建 .slnx 文件。在 .NET 9 中,必须显式迁移或指定格式。

示例 .slnx 文件

<Solution>
  <Folder Name="/build/">
    <File Path="Directory.Build.props" />
    <File Path="Directory.Packages.props" />
    <File Path="global.json" />
    <File Path="NuGet.Config" />
    <File Path="README.md" />
  </Folder>
  <Folder Name="/src/">
    <Project Path="src/MyApp/MyApp.csproj" />
    <Project Path="src/MyApp.Core/MyApp.Core.csproj" />
  </Folder>
  <Folder Name="/tests/">
    <Project Path="tests/MyApp.Tests/MyApp.Tests.csproj" />
  </Folder>
</Solution>

从 .sln 迁移到 .slnx

使用 dotnet sln migrate 命令转换现有解决方案:

# 迁移特定解决方案文件
dotnet sln MySolution.sln migrate

# 或者如果目录中只有一个 .sln,则直接运行:
dotnet sln migrate

重要: 不要在同一个仓库中保留 .sln.slnx 文件。这会导致自动解决方案检测问题,并可能导致同步问题。迁移后,删除旧的 .sln 文件。

你也可以在 Visual Studio 中迁移:

  1. 打开解决方案
  2. 在解决方案资源管理器中选择解决方案
  3. 转到 文件 > 另存为…
  4. 将 “保存类型” 更改为 Xml 解决方案文件 (*.slnx)

创建新的 .slnx 解决方案

# .NET 10+:默认创建 .slnx
dotnet new sln --name MySolution

# .NET 9:显式指定格式
dotnet new sln --name MySolution --format slnx

# 添加项目(两种格式的工作方式相同)
dotnet sln add src/MyApp/MyApp.csproj

建议

如果您使用的是 .NET 9.0.200 或更高版本,请将您的解决方案迁移到 .slnx。 好处是显著的:

  • 显著减少合并冲突(没有随机变化的 GUIDs)
  • 人类可读且可在任何文本编辑器中编辑
  • 与现代 .csproj 格式一致
  • 在拉取请求中更好的 diff/审查体验

Directory.Build.props

Directory.Build.props 提供集中式构建配置,适用于目录树中的所有项目。将其放置在解决方案根目录。

完整示例

<Project>
  <!-- 元数据 -->
  <PropertyGroup>
    <Authors>您的团队</Authors>
    <Company>贵公司</Company>
    <!-- 动态版权年份 - 自动更新 -->
    <Copyright>版权所有 © 2020-$([System.DateTime]::Now.Year) 贵公司</Copyright>
    <Product>您的产品</Product>
    <PackageProjectUrl>https://github.com/yourorg/yourrepo</PackageProjectUrl>
    <RepositoryUrl>https://github.com/yourorg/yourrepo</RepositoryUrl>
    <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
    <PackageTags>your;tags;here</PackageTags>
  </PropertyGroup>

  <!-- C# 语言设置 -->
  <PropertyGroup>
    <LangVersion>latest</LangVersion>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <NoWarn>$(NoWarn);CS1591</NoWarn> <!-- 缺少 XML 注释 -->
  </PropertyGroup>

  <!-- 版本管理 -->
  <PropertyGroup>
    <VersionPrefix>1.0.0</VersionPrefix>
    <PackageReleaseNotes>见 RELEASE_NOTES.md</PackageReleaseNotes>
  </PropertyGroup>

  <!-- 目标框架定义(可重用属性) -->
  <PropertyGroup>
    <NetStandardLibVersion>netstandard2.0</NetStandardLibVersion>
    <NetLibVersion>net8.0</NetLibVersion>
    <NetTestVersion>net9.0</NetTestVersion>
  </PropertyGroup>

  <!-- SourceLink 配置 -->
  <PropertyGroup>
    <PublishRepositoryUrl>true</PublishRepositoryUrl>
    <EmbedUntrackedSources>true</EmbedUntrackedSources>
    <IncludeSymbols>true</IncludeSymbols>
    <SymbolPackageFormat>snupkg</SymbolPackageFormat>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
  </ItemGroup>

  <!-- NuGet 包资产 -->
  <ItemGroup>
    <None Include="$(MSBuildThisFileDirectory)logo.png" Pack="true" PackagePath="\" />
    <None Include="$(MSBuildThisFileDirectory)README.md" Pack="true" PackagePath="\" />
  </ItemGroup>

  <PropertyGroup>
    <PackageIcon>logo.png</PackageIcon>
    <PackageReadmeFile>README.md</PackageReadmeFile>
  </PropertyGroup>

  <!-- 全局使用语句 -->
  <ItemGroup>
    <Using Include="System.Collections.Immutable" />
  </ItemGroup>
</Project>

关键模式

动态版权年份

<Copyright>版权所有 © 2020-$([System.DateTime]::Now.Year) 贵公司</Copyright>

使用 MSBuild 属性函数在构建时插入当前年份。无需手动更新。

可重用目标框架属性

一次定义目标框架,到处引用:

<!-- 在 Directory.Build.props 中 -->
<PropertyGroup>
  <NetLibVersion>net8.0</NetLibVersion>
  <NetTestVersion>net9.0</NetTestVersion>
</PropertyGroup>

<!-- 在 MyApp.csproj 中 -->
<PropertyGroup>
  <TargetFramework>$(NetLibVersion)</TargetFramework>
</PropertyGroup>

<!-- 在 MyApp.Tests.csproj 中 -->
<PropertyGroup>
  <TargetFramework>$(NetTestVersion)</TargetFramework>
</PropertyGroup>

SourceLink 用于 NuGet 包

SourceLink 启用 NuGet 包的逐步调试:

<PropertyGroup>
  <PublishRepositoryUrl>true</PublishRepositoryUrl>
  <EmbedUntrackedSources>true</EmbedUntrackedSources>
  <IncludeSymbols>true</IncludeSymbols>
  <SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>

<ItemGroup>
  <!-- 选择适合您的源控制的提供商 -->
  <PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
  <!-- 或:Microsoft.SourceLink.AzureRepos.Git -->
  <!-- 或:Microsoft.SourceLink.GitLab -->
  <!-- 或:Microsoft.SourceLink.Bitbucket.Git -->
</ItemGroup>

Directory.Packages.props - 集中包管理

集中包管理(CPM)提供所有 NuGet 包版本的单一真实来源。

设置

<Project>
  <PropertyGroup>
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
  </PropertyGroup>

  <!-- 为相关包定义版本变量 -->
  <PropertyGroup>
    <AkkaVersion>1.5.35</AkkaVersion>
    <AspireVersion>9.1.0</AspireVersion>
  </PropertyGroup>

  <!-- 应用程序依赖项 -->
  <ItemGroup Label="应用程序依赖项">
    <PackageVersion Include="Akka" Version="$(AkkaVersion)" />
    <PackageVersion Include="Akka.Cluster" Version="$(AkkaVersion)" />
    <PackageVersion Include="Akka.Persistence" Version="$(AkkaVersion)" />
    <PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
  </ItemGroup>

  <!-- 构建/工具依赖项 -->
  <ItemGroup Label="构建依赖项">
    <PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
  </ItemGroup>

  <!-- 测试依赖项 -->
  <ItemGroup Label="测试依赖项">
    <PackageVersion Include="xunit" Version="2.9.3" />
    <PackageVersion Include="xunit.runner.visualstudio" Version="3.0.1" />
    <PackageVersion Include="FluentAssertions" Version="7.0.0" />
    <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
    <PackageVersion Include="coverlet.collector" Version="6.0.3" />
  </ItemGroup>
</Project>

消费包(无需版本)

<!-- 在 MyApp.csproj 中 -->
<ItemGroup>
  <PackageReference Include="Akka" />
  <PackageReference Include="Akka.Cluster" />
  <PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>

<!-- 在 MyApp.Tests.csproj 中 -->
<ItemGroup>
  <PackageReference Include="xunit" />
  <PackageReference Include="FluentAssertions" />
  <PackageReference Include="Microsoft.NET.Test.Sdk" />
</ItemGroup>

好处

  1. 单一真实来源 - 所有版本在一个文件中
  2. 无版本漂移 - 所有项目使用相同版本
  3. 易于更新 - 一次更改,到处应用
  4. 分组包 - 相关包的版本变量(例如,所有 Akka 包)

global.json - SDK 版本固定

固定 .NET SDK 版本,以在所有环境中实现一致构建。

{
  "sdk": {
    "version": "9.0.200",
    "rollForward": "latestFeature"
  }
}

滚动策略

策略 行为
disable 需要确切版本
patch 相同主次版本,最新补丁
feature 相同主版本,最新的次.补丁
latestFeature 相同主版本,最新的功能带
minor 相同主版本,最新次版本
latestMinor 相同主版本,最新次版本
major 最新 SDK(不推荐)

推荐: latestFeature - 允许在同一功能带内进行补丁更新。


版本管理与 RELEASE_NOTES.md

发布说明格式

#### 1.2.0 2025年1月15日 ####

- 添加新功能 X
- 修复 Y 中的错误
- 提高 Z 的性能

#### 1.1.0 2024年12月10日 ####

- 初始发布,功能 A、B、C

解析脚本(getReleaseNotes.ps1)

function Get-ReleaseNotes {
    param (
        [Parameter(Mandatory=$true)]
        [string]$MarkdownFile
    )

    $content = Get-Content -Path $MarkdownFile -Raw
    $sections = $content -split "####"

    $result = [PSCustomObject]@{
        Version      = $null
        Date         = $null
        ReleaseNotes = $null
    }

    if ($sections.Count -ge 3) {
        $header = $sections[1].Trim()
        $releaseNotes = $sections[2].Trim()

        $headerParts = $header -split " ", 2
        if ($headerParts.Count -eq 2) {
            $result.Version = $headerParts[0]
            $result.Date = $headerParts[1]
        }

        $result.ReleaseNotes = $releaseNotes
    }

    return $result
}

版本提升脚本(bumpVersion.ps1)

function UpdateVersionAndReleaseNotes {
    param (
        [Parameter(Mandatory=$true)]
        [PSCustomObject]$ReleaseNotesResult,
        [Parameter(Mandatory=$true)]
        [string]$XmlFilePath
    )

    $xmlContent = New-Object XML
    $xmlContent.Load($XmlFilePath)

    # 更新 VersionPrefix
    $versionElement = $xmlContent.SelectSingleNode("//VersionPrefix")
    $versionElement.InnerText = $ReleaseNotesResult.Version

    # 更新 PackageReleaseNotes
    $notesElement = $xmlContent.SelectSingleNode("//PackageReleaseNotes")
    $notesElement.InnerText = $ReleaseNotesResult.ReleaseNotes

    $xmlContent.Save($XmlFilePath)
}

构建脚本(build.ps1)

# 加载辅助脚本
. "$PSScriptRoot\scripts\getReleaseNotes.ps1"
. "$PSScriptRoot\scripts\bumpVersion.ps1"

# 解析发布说明并更新 Directory.Build.props
$releaseNotes = Get-ReleaseNotes -MarkdownFile (Join-Path -Path $PSScriptRoot -ChildPath "RELEASE_NOTES.md")
UpdateVersionAndReleaseNotes -ReleaseNotesResult $releaseNotes -XmlFilePath (Join-Path -Path $PSScriptRoot -ChildPath "Directory.Build.props")

Write-Output "更新到版本 $($releaseNotes.Version)"

CI/CD 集成

# GitHub Actions 示例
- name: 从发布说明更新版本
  shell: pwsh
  run: ./build.ps1

- name: 构建
  run: dotnet build -c Release

- name: 打包并标记版本
  run: dotnet pack -c Release /p:PackageVersion=${{ github.ref_name }}

- name: 推送到 NuGet
  run: dotnet nuget push **/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json

NuGet.Config

配置 NuGet 源和行为:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <solution>
    <add key="disableSourceControlIntegration" value="true" />
  </solution>

  <packageSources>
    <clear />
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
    <!-- 如果需要,添加私有源 -->
    <!-- <add key="MyCompany" value="https://pkgs.dev.azure.com/myorg/_packaging/myfeed/nuget/v3/index.json" /> -->
  </packageSources>
</configuration>

关键设置:

  • <clear /> - 移除继承的/默认源,以实现可复现的构建
  • disableSourceControlIntegration - 防止 TFS/Git 集成问题

完整项目结构

MySolution/
├── .config/
│   └── dotnet-tools.json           # 本地 .NET 工具
├── .github/
│   └── workflows/
│       ├── pr-validation.yml       # PR 检查
│       └── release.yml             # NuGet 发布
├── scripts/
│   ├── getReleaseNotes.ps1         # 解析 RELEASE_NOTES.md
│   └── bumpVersion.ps1             # 更新 Directory.Build.props
├── src/
│   ├── MyApp/
│   │   └── MyApp.csproj
│   └── MyApp.Core/
│       └── MyApp.Core.csproj
├── tests/
│   └── MyApp.Tests/
│       └── MyApp.Tests.csproj
├── Directory.Build.props           # 集中式构建配置
├── Directory.Packages.props        # 集中包版本
├── MySolution.slnx                 # 现代解决方案文件
├── global.json                     # SDK 版本固定
├── NuGet.Config                    # 包源配置
├── build.ps1                       # 构建协调脚本
├── RELEASE_NOTES.md                # 版本历史(由构建解析)
├── README.md                       # 项目文档
└── logo.png                        # 包图标

快速参考

文件 目的
MySolution.slnx 现代 XML 解决方案文件
Directory.Build.props 集中式构建属性
Directory.Packages.props 集中包版本管理
global.json SDK 版本固定
NuGet.Config 包源配置
RELEASE_NOTES.md 版本历史(由构建解析)
build.ps1 构建协调脚本
.config/dotnet-tools.json 本地 .NET 工具