mcpField
D
dotnet-solution-navigation

dotnet-solution-navigation

github.com/wshaddix开发8672 次安装35 周前更新
代码仓库

关于 dotnet-solution-navigation

帮助 Agent 浏览 .NET 解决方案的技能,涵盖入口点定位、解决方案文件解析、项目依赖追踪和配置查找,并提供常见布局的识别方法与命令。

主要功能

  1. 01发现应用入口点
  2. 02读取 .sln 和 .slnx 文件
  3. 03追踪项目依赖
  4. 04定位配置文件
  5. 05识别常见解决方案布局

适用场景

  1. 01熟悉陌生的 .NET 代码库
  2. 02查找共享变更影响的项目
  3. 03定位运行时和构建配置

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/wshaddix/dotnet-skills --skill dotnet-solution-navigation

安装后在 Claude Code 中输入/dotnet-solution-navigation确认已加载。

详细介绍

dotnet-solution-navigation

概述 / 范围边界

帮助 Agent 熟悉 .NET 解决方案:查找入口点、解析解决方案文件、遍历项目依赖项、定位配置文件,以及识别常见的解决方案布局。每个小节都包含发现命令/启发式方法和示例输出。

不在范围内: 项目文件结构和修改(由 [skill:dotnet-csproj-reading] 负责)。项目组织决策和 SDK 选择(由 [skill:dotnet-project-structure] 负责)。测试框架配置和测试类型决策(由 [skill:dotnet-testing-strategy] 负责)。

前置条件

.NET 8.0+ SDK。dotnet CLI 已在 PATH 中可用。熟悉 SDK 风格项目。

交叉引用:[skill:dotnet-project-structure] 提供项目组织指南;[skill:dotnet-csproj-reading] 介绍如何读取和修改导航过程中找到的 .csproj 文件;[skill:dotnet-testing-strategy] 介绍测试项目识别和测试类型决策。


小节 1:发现入口点

.NET 应用可以采用多种启动模式。不要假设每个应用都有传统的 Program.cs 和 Main 方法。

模式 1:带 Main 方法的传统 Program.cs

用于较旧的项目、工作服务,以及需要显式控制托管过程的情况。

发现命令:

# Find Program.cs files containing a Main method
grep -rn "static.*void Main\|static.*Task Main\|static.*async.*Main" --include="*.cs" .

示例输出:

src/MyApp.Worker/Program.cs:5:    public static async Task Main(string[] args)
src/MyApp.Console/Program.cs:3:    static void Main(string[] args)
模式 2:顶级语句(C# 9+)

现代 .NET 项目(自 .NET 6 起的模板)使用顶级语句——文件中没有类或 Main 方法,只有可执行代码。

发现命令:

# Find Program.cs files that do NOT contain class/namespace declarations
# (top-level statements have no enclosing class)
for f in $(find . -name "Program.cs" -not -path "*/obj/*" -not -path "*/bin/*"); do
  if ! grep -Eq '^[[:space:]]*(class|namespace)[[:space:]]' "$f" 2>/dev/null; then
    echo "Top-level: $f"
  fi
done

示例输出:

Top-level: ./src/MyApp.Api/Program.cs
Top-level: ./src/MyApp.Web/Program.cs

顶级 Program.cs 的典型内容:

// No namespace, no class, no Main -- this IS the entry point
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();
模式 3:工作服务和后台宿主

工作服务使用 Host.CreateDefaultBuilder 或 Host.CreateApplicationBuilder,且不使用 Web 服务器。它们的输出类型为 Exe,SDK 为 Microsoft.NET.Sdk.Worker。

发现命令:

# Find worker service projects by SDK type
grep -rn 'Sdk="Microsoft.NET.Sdk.Worker"' --include="*.csproj" .

# Or find IHostedService/BackgroundService implementations
grep -rn "BackgroundService\|IHostedService" --include="*.cs" . | grep -v "obj/" | grep -v "bin/"

示例输出:

src/MyApp.Worker/MyApp.Worker.csproj:1:<Project Sdk="Microsoft.NET.Sdk.Worker">
src/MyApp.Worker/Services/OrderProcessor.cs:8:public class OrderProcessor : BackgroundService
src/MyApp.Worker/Services/EmailSender.cs:5:public class EmailSender : IHostedService
模式 4:测试项目

测试项目是运行 dotnet test 的入口点。它们可能完全没有 Program.cs——入口点由测试运行器提供。

发现命令:

# Find test projects by IsTestProject property or test SDK references
grep -rn "<IsTestProject>true</IsTestProject>" --include="*.csproj" .
grep -rn "Microsoft.NET.Test.Sdk\|xunit\|NUnit\|MSTest" --include="*.csproj" . | grep -v "obj/"  # Matches both xunit.v3 and legacy xunit

示例输出:

tests/MyApp.Api.Tests/MyApp.Api.Tests.csproj:5:    <IsTestProject>true</IsTestProject>
tests/MyApp.Core.Tests/MyApp.Core.Tests.csproj:8:    <PackageReference Include="xunit.v3" />
总结启发式

熟悉新的 .NET 解决方案时,依次运行以下命令:

# 1. Find all .csproj files
find . -name "*.csproj" -not -path "*/obj/*" | sort

# 2. Identify output types (Exe = app entry point, Library = dependency)
grep -rn "<OutputType>" --include="*.csproj" .

# 3. Find all Program.cs files
find . -name "Program.cs" -not -path "*/obj/*" -not -path "*/bin/*"

# 4. Identify test projects
grep -rn "<IsTestProject>true" --include="*.csproj" .

小节 2:解决方案文件格式

.NET 解决方案使用 .sln(基于文本的旧格式)或 .slnx(基于 XML 的 .NET 9+ 预览格式)。这两种文件都会列出项目及其关系。

.sln 格式

传统的解决方案格式是采用自定义语法的文本文件(不是 XML)。

发现和解析命令:

# Find solution files
find . -name "*.sln" -maxdepth 2

# List all projects in a solution using dotnet CLI
dotnet sln list
# Or specify the solution file explicitly:
dotnet sln MyApp.sln list

dotnet sln list 的示例输出:

Project(s)
----------
src/MyApp.Api/MyApp.Api.csproj
src/MyApp.Core/MyApp.Core.csproj
src/MyApp.Infrastructure/MyApp.Infrastructure.csproj
tests/MyApp.Api.Tests/MyApp.Api.Tests.csproj
tests/MyApp.Core.Tests/MyApp.Core.Tests.csproj

直接读取 .sln 文件(在无法使用 dotnet sln list 时很有用):

# Extract project entries from .sln file
grep "^Project(" MyApp.sln

示例输出:

Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyApp.Api", "src\MyApp.Api\MyApp.Api.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyApp.Core", "src\MyApp.Core\MyApp.Core.csproj", "{B2C3D4E5-F6A7-8901-BCDE-F12345678901}"

GUID {FAE04EC0-...} 用于标识 C# 项目。第二个值是 .csproj 文件的相对路径。

.slnx 格式(.NET 9+)

.slnx 格式是基于 XML 的解决方案文件,于 .NET 9 中作为预览功能引入。

发现和解析命令:

# Find .slnx files
find . -name "*.slnx" -maxdepth 2

# dotnet sln commands work with .slnx files too
dotnet sln MyApp.slnx list

.slnx 内容示例:

<Solution>
  <Folder Name="/src/">
    <Project Path="src/MyApp.Api/MyApp.Api.csproj" />
    <Project Path="src/MyApp.Core/MyApp.Core.csproj" />
  </Folder>
  <Folder Name="/tests/">
    <Project Path="tests/MyApp.Api.Tests/MyApp.Api.Tests.csproj" />
  </Folder>
</Solution>

与 .sln 的主要区别:

特性.sln.slnx
格式自定义文本XML
可读性低(GUID、自定义语法)高(简洁的 XML)
可用性所有 .NET 版本.NET 9+ 预览版
工具支持完全支持部分支持(逐步完善)
解决方案文件夹嵌套 GUID 引用<Folder> 元素
不存在解决方案文件时

某些代码仓库仅使用独立的 .csproj 文件,而没有 .sln。请从项目目录进行生成和运行:

# If no .sln exists, find all .csproj files and build individually
find . -name "*.csproj" -not -path "*/obj/*" | sort
dotnet build src/MyApp.Api/MyApp.Api.csproj

小节 3:遍历项目依赖项

了解 ProjectReference 链对于确定生成顺序、查找共享代码和识别更改影响至关重要。

发现命令
# Find all ProjectReference entries across the solution
grep -rn "<ProjectReference" --include="*.csproj" . | grep -v "obj/"

示例输出:

src/MyApp.Api/MyApp.Api.csproj:12:    <ProjectReference Include="../MyApp.Core/MyApp.Core.csproj" />
src/MyApp.Api/MyApp.Api.csproj:13:    <ProjectReference Include="../MyApp.Infrastructure/MyApp.Infrastructure.csproj" />
src/MyApp.Infrastructure/MyApp.Infrastructure.csproj:10:    <ProjectReference Include="../MyApp.Core/MyApp.Core.csproj" />
tests/MyApp.Api.Tests/MyApp.Api.Tests.csproj:14:    <ProjectReference Include="../../src/MyApp.Api/MyApp.Api.csproj" />
构建依赖关系图

根据上述输出,依赖关系图如下:

MyApp.Api.Tests
  -> MyApp.Api
       -> MyApp.Core
       -> MyApp.Infrastructure
            -> MyApp.Core

使用 dotnet list reference 自动遍历:

# List direct references for a specific project
dotnet list src/MyApp.Api/MyApp.Api.csproj reference

输出示例:

Project reference(s)
--------------------
../MyApp.Core/MyApp.Core.csproj
../MyApp.Infrastructure/MyApp.Infrastructure.csproj

完整的传递依赖分析:

# Build the full dependency tree by traversing transitively
# Start from the top-level project and follow each reference
dotnet list src/MyApp.Api/MyApp.Api.csproj reference
dotnet list src/MyApp.Infrastructure/MyApp.Infrastructure.csproj reference
# Continue until you reach projects with no ProjectReference entries
影响分析

修改 MyApp.Core 这类共享项目时,所有直接或间接引用它的项目都会受到影响:

# Find all projects that reference a specific project
grep -rn "MyApp.Core.csproj" --include="*.csproj" . | grep -v "obj/"

输出示例:

src/MyApp.Api/MyApp.Api.csproj:12:    <ProjectReference Include="../MyApp.Core/MyApp.Core.csproj" />
src/MyApp.Infrastructure/MyApp.Infrastructure.csproj:10:    <ProjectReference Include="../MyApp.Core/MyApp.Core.csproj" />
tests/MyApp.Core.Tests/MyApp.Core.Tests.csproj:14:    <ProjectReference Include="../../src/MyApp.Core/MyApp.Core.csproj" />

这意味着修改 MyApp.Core 后,需要测试 MyApp.Api、MyApp.Infrastructure 和 MyApp.Core.Tests。


子节 4:配置文件位置

.NET 项目会在解决方案各处使用多个配置文件。了解这些文件的位置对于理解应用程序的行为至关重要。

appsettings*.json

查找命令:

# Find all appsettings files
find . -name "appsettings*.json" -not -path "*/obj/*" -not -path "*/bin/*" | sort

输出示例:

./src/MyApp.Api/appsettings.json
./src/MyApp.Api/appsettings.Development.json
./src/MyApp.Api/appsettings.Production.json
./src/MyApp.Worker/appsettings.json

**关键行为:**环境专属文件(appsettings.{ENVIRONMENT}.json)会覆盖基础 appsettings.json 中的值。环境通过 DOTNET_ENVIRONMENT 或 ASPNETCORE_ENVIRONMENT 设置。

launchSettings.json

查找命令:

# Find launch settings (inside Properties/ folder of each project)
find . -name "launchSettings.json" -not -path "*/obj/*" -not -path "*/bin/*"

输出示例:

./src/MyApp.Api/Properties/launchSettings.json
./src/MyApp.Web/Properties/launchSettings.json

关键行为:dotnet run 和 Visual Studio 使用此文件配置启动配置文件(端口、环境变量、启动 URL)。此文件不会部署到生产环境。

Directory.Build.props 和 Directory.Build.targets

查找命令:

# Find all Directory.Build.props/targets files (may exist at multiple levels)
find . -name "Directory.Build.props" -o -name "Directory.Build.targets" | sort

输出示例:

./Directory.Build.props
./Directory.Build.targets
./src/Directory.Build.props
./tests/Directory.Build.props

**关键行为:**MSBuild 会从项目目录开始向上查找,并导入找到的最近文件。嵌套文件会遮蔽父级文件,除非它们显式导入父级文件(有关链接方式,请参阅 [skill:dotnet-csproj-reading])。

其他配置文件
# Find all .NET configuration files in one sweep
find . \( -name "nuget.config" -o -name "global.json" -o -name ".editorconfig" \
  -o -name "Directory.Packages.props" \) -not -path "*/obj/*" | sort

输出示例:

./.editorconfig
./Directory.Packages.props
./global.json
./nuget.config
./src/.editorconfig
文件用途解析方式
nuget.configNuGet 包源和映射从项目目录开始向上分层查找
global.json固定 SDK 版本向上查找最近的文件
.editorconfig代码风格和分析器严重性分层合并(各区段逐级向上合并)
Directory.Packages.props集中管理包版本从项目目录开始向上分层查找

子节 5:常见解决方案布局

识别布局模式有助于 Agent 更快地浏览陌生代码库。

模式 1:src/tests 布局

这是最常见的布局。源项目位于 src/,测试项目位于 tests/,且名称相互对应。

MyApp/
  MyApp.sln
  Directory.Build.props
  Directory.Packages.props
  global.json
  nuget.config
  .editorconfig
  src/
    MyApp.Api/
      MyApp.Api.csproj
      Program.cs
      Controllers/
      Services/
    MyApp.Core/
      MyApp.Core.csproj
      Models/
      Interfaces/
    MyApp.Infrastructure/
      MyApp.Infrastructure.csproj
      Data/
      Repositories/
  tests/
    MyApp.Api.Tests/
      MyApp.Api.Tests.csproj
    MyApp.Core.Tests/
      MyApp.Core.Tests.csproj
  docs/
    architecture.md

判断依据:

  • 根目录下有 src/ 和 tests/ 目录。
  • 测试项目名称与源项目名称对应,并带有 .Tests 后缀。
  • 根目录下有共享构建配置(Directory.Build.props、global.json)。

查找:

# Detect src/tests layout
ls -d src/ tests/ 2>/dev/null && echo "src/tests layout detected"
模式 2:垂直切片布局

按功能而不是技术层组织代码。每个切片都包含自己的模型、处理程序和端点。

MyApp/
  MyApp.sln
  src/
    MyApp.Api/
      MyApp.Api.csproj
      Program.cs
      Features/
        Orders/
          CreateOrder.cs          # Handler + request + response
          GetOrder.cs
          OrderValidator.cs
          OrderEndpoints.cs       # Minimal API endpoint mapping
        Products/
          CreateProduct.cs
          ListProducts.cs
          ProductEndpoints.cs
      Common/
        Behaviors/
          ValidationBehavior.cs
        Middleware/
          ExceptionMiddleware.cs
  tests/
    MyApp.Api.Tests/
      Features/
        Orders/
          CreateOrderTests.cs
          GetOrderTests.cs

判断依据:

  • 项目内有 Features/ 目录。
  • 每个功能文件夹都包含多个相关文件(处理程序、验证器、端点)。
  • 测试的目录结构与功能文件夹结构对应。

查找:

# Detect vertical slice layout
find . -type d -name "Features" -not -path "*/obj/*" -not -path "*/bin/*"
模式 3:模块化单体

在单个解决方案中,将多个有界上下文作为独立项目,并通过显式接口或共享消息总线进行通信。

MyApp/
  MyApp.sln
  src/
    MyApp.Host/
      MyApp.Host.csproj          # Composition root -- references all modules
      Program.cs
    Modules/
      Ordering/
        MyApp.Ordering/
          MyApp.Ordering.csproj
          OrderingModule.cs       # Module registration (DI, endpoints)
          Domain/
          Application/
          Infrastructure/
        MyApp.Ordering.Tests/
      Catalog/
        MyApp.Catalog/
          MyApp.Catalog.csproj
          CatalogModule.cs
          Domain/
          Application/
          Infrastructure/
        MyApp.Catalog.Tests/
    MyApp.Shared/
      MyApp.Shared.csproj        # Cross-cutting contracts (events, interfaces)

判断依据:

  • 有包含独立有界上下文的 Modules/ 目录。
  • 有一个引用所有模块的 Host 或 Startup 项目。
  • 有一个用于跨模块协定的 Shared 项目。

查找:

# Detect modular monolith layout
find . -type d -name "Modules" -not -path "*/obj/*" -not -path "*/bin/*"
# Or look for module registration patterns
grep -rn "Module\|AddModule\|RegisterModule" --include="*.cs" . | grep -v "obj/" | head -10

Slopwatch 反模式

在发现测试项目时出现这些模式,表明 Agent 正在掩盖测试缺口,而不是解决问题。有关检测这些模式的自动化质量门禁,请参阅 [skill:dotnet-slopwatch]。

测试项目发现过程中被禁用或跳过的测试

浏览解决方案并识别测试项目时,注意检查是否有测试存在却被悄悄禁用:

// RED FLAG: skipped tests that will not run during dotnet test
[Fact(Skip = "Flaky -- revisit later")]
public async Task ProcessOrder_ConcurrentRequests_HandledCorrectly() { }

// RED FLAG: entire test class disabled via conditional compilation
#if false
public class OrderIntegrationTests
{
    [Fact]
    public async Task CreateOrder_PersistsToDatabase() { }
}
#endif

// RED FLAG: commented-out test methods
// [Fact]
// public void CalculateDiscount_NegativeAmount_ThrowsException() { }

用于检查禁用测试的发现命令:

# Find skipped tests
grep -rEn 'Skip[[:space:]]*=' --include="*.cs" . | grep -v "obj/" | grep -v "bin/"

# Find tests hidden behind #if false
grep -rn "#if false" --include="*.cs" . | grep -v "obj/" | grep -v "bin/"

# Find commented-out test attributes
grep -rEn '//[[:space:]]*\[(Fact|Theory|Test)\]' --include="*.cs" . | grep -v "obj/" | grep -v "bin/"

修复方法: 调查测试被禁用的原因。如果测试因时序问题而不稳定,请解决非确定性问题,或使用 [Retry](xUnit v3)。如果测试针对的功能已被移除,请删除测试。切勿让禁用的测试成为不可见的技术债务。


交叉引用

  • [skill:dotnet-project-structure] -- 项目组织、SDK 选择、解决方案布局决策
  • [skill:dotnet-csproj-reading] -- 阅读和修改浏览过程中发现的 .csproj 文件
  • [skill:dotnet-testing-strategy] -- 测试项目识别、测试类型、测试组织

参考资料

评论

支持 Markdown