Community코딩 & 개발github.com

dotnet-project-structure

현대적인 .NET 프로젝트 구조 (.slnx 솔루션 형식, Directory.Build.props, 중앙 패키지 관리, SourceLink, RELEASE_NOTES.md를 통한 버전 관리, global.json을 이용한 SDK 고정)

dotnet-project-structure란 무엇인가요?

이 스킬은 최신 .NET 모범 사례를 사용하여 .NET 솔루션을 설정하는 방법을 안내합니다. 주요 기능으로는 XML 기반의 .slnx 솔루션 형식, 중앙 집중식 빌드 속성을 위한 Directory.Build.props, 모든 NuGet 패키지 버전을 한 곳에서 관리하는 중앙 패키지 관리(CPM), 디버깅 및 NuGet 패키지를 위한 SourceLink 설정, RELEASE_NOTES.md를 사용한 자동 버전 관리, global.json을 통한 일관된 SDK 버전 고정 등이 있습니다. 이 스킬은 Claude Code, Cursor, Codex와 같은 AI 에이전트가 새로운 프로젝트를 초기화하거나, 기존 솔루션을 현대화하거나, CI/CD 파이프라인을 구성할 때 유용합니다. 명확한 마크다운 문서, 코드 예제, 마이그레이션 지침을 제공하므로 AI 에이전트가 사람의 개입 없이도 이 작업을 정확하게 수행할 수 있습니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Aaronontheweb/dotnet-skills/tree/main/skills/project-structure

Installed? Explore more 코딩 & 개발 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

.NET 프로젝트 구조 및 빌드 구성

이 스킬을 사용해야 하는 경우

다음과 같은 경우 이 스킬을 사용하세요:

  • 최신 모범 사례로 새 .NET 솔루션을 설정할 때
  • 여러 프로젝트에 걸쳐 중앙 집중식 빌드 속성을 구성할 때
  • 중앙 패키지 버전 관리를 구현할 때
  • 디버깅 및 NuGet 패키지를 위해 SourceLink를 설정할 때
  • 릴리스 노트로 버전 관리를 자동화할 때
  • 일관된 빌드를 위해 SDK 버전을 고정할 때

관련 스킬

  • dotnet-local-tools - dotnet-tools.json으로 로컬 .NET 도구 관리
  • microsoft-extensions-configuration - 구성 유효성 검사 패턴

솔루션 파일 형식 (.slnx)

.slnx 형식은 .NET 9에서 도입된 최신 XML 기반 솔루션 파일 형식입니다. 기존 .sln 형식을 대체합니다.

기존 .sln 대비 장점

항목.sln (레거시).slnx (최신)
형식사용자 지정 텍스트 형식표준 XML
가독성GUID, 난해한 구문깔끔하고 사람이 읽기 쉬움
버전 관리diff/merge 어려움diff/merge 용이
편집IDE 필요모든 텍스트 편집기 가능

버전 요구 사항

도구최소 버전
.NET SDK9.0.200
Visual Studio17.13
MSBuildVisual Studio Build Tools 17.13

참고: .NET 10부터 dotnet new sln은 기본적으로 .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로 마이그레이션하세요. 장점은 상당합니다:

  • 병합 충돌이 훨씬 적음 (무작위 GUID 변경 없음)
  • 모든 텍스트 편집기에서 사람이 읽고 편집 가능
  • 최신 .csproj 형식과 일관성
  • 풀 리퀘스트에서 더 나은 diff/리뷰 경험

Directory.Build.props

Directory.Build.props는 디렉터리 트리의 모든 프로젝트에 적용되는 중앙 집중식 빌드 구성을 제공합니다. 솔루션 루트에 배치하세요.

전체 예제

<Project>
  <!-- 메타데이터 -->
  <PropertyGroup>
    <Authors>Your Team</Authors>
    <Company>Your Company</Company>
    <!-- 동적 저작권 연도 - 자동 업데이트 -->
    <Copyright>Copyright © 2020-$([System.DateTime]::Now.Year) Your Company</Copyright>
    <Product>Your 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>See 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>

  <!-- 전역 Using 문 -->
  <ItemGroup>
    <Using Include="System.Collections.Immutable" />
  </ItemGroup>
</Project>

주요 패턴

동적 저작권 연도

<Copyright>Copyright © 2020-$([System.DateTime]::Now.Year) Your Company</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>

NuGet 패키지를 위한 SourceLink

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="App Dependencies">
    <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="Build Dependencies">
    <PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
  </ItemGroup>

  <!-- 테스트 종속성 -->
  <ItemGroup Label="Test Dependencies">
    <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 "Updated to version $($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                 # XML 솔루션 파일
├── global.json                     # SDK 버전 고정
├── NuGet.Config                    # NuGet 소스 구성
├── RELEASE_NOTES.md                # 버전 관리
├── README.md                       # 프로젝트 문서
├── logo.png                        # NuGet 패키지 아이콘
└── LICENSE                         # 오픈 소스 라이선스

빠른 시작 템플릿

새 솔루션을 만들기 위한 전체 설정 명령어

# 솔루션 생성
dotnet new sln --name MySolution --format slnx

# 프로젝트 추가
dotnet new classlib -n MyApp.Core -o src/MyApp.Core --framework net8.0
dotnet new webapi -n MyApp -o src/MyApp --framework net9.0
dotnet new xunit -n MyApp.Tests -o tests/MyApp.Tests --framework net9.0

# 솔루션에 프로젝트 추가
dotnet sln add src/MyApp.Core/MyApp.Core.csproj
dotnet sln add src/MyApp/MyApp.csproj
dotnet sln add tests/MyApp.Tests/MyApp.Tests.csproj

# 필수 구성 파일 생성 (Directories.Build.props, Directory.Packages.props, global.json, NuGet.Config, RELEASE_NOTES.md)
# 위 예제의 내용을 사용하여 이 파일들을 만드세요.

문제 해결

문제해결 방법
dotnet sln migrate 실패SDK 9.0.200+를 사용 중인지 확인하세요.
솔루션에 중복 프로젝트.sln 파일 하나와 .slnx 파일 하나만 있는지 확인하세요.
CPM에서 패키지 버전 누락Directory.Packages.props에서 ManagePackageVersionsCentrally가 true로 설정되었는지 확인하세요.
SourceLink가 작동하지 않음리포지토리 URL이 Directory.Build.props에서 올바르게 설정되었는지 확인하세요.
CI에서 SDK 버전 불일치global.json이 소스 제어에 커밋되었는지 확인하세요.

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

akka-hosting-actor-patterns

Patterns for building entity actors with Akka.Hosting - GenericChildPerEntityParent, message extractors, cluster sharding abstraction, akka-reminders, and ITimeProvider. Supports both local testing and clustered production modes.

akka-net-aspire-configuration

Configure Akka.NET with .NET Aspire for local development and production deployments. Covers actor system setup, clustering, persistence, Akka.Management integration, and Aspire orchestration patterns.

akka-net-best-practices

Critical Akka.NET best practices including EventStream vs DistributedPubSub, supervision strategies, error handling, Props vs DependencyResolver, work distribution patterns, and cluster/local mode abstractions for testability.

akka-net-management

Akka.Management for cluster bootstrapping, service discovery (Kubernetes, Azure, Config), health checks, and dynamic cluster formation without static seed nodes.

akka-net-testing-patterns

Write unit and integration tests for Akka.NET actors using modern Akka.Hosting.TestKit patterns. Covers dependency injection, TestProbes, persistence testing, and actor interaction verification. Includes guidance on when to use traditional TestKit.

api-design

Design stable, compatible public APIs using extend-only design principles. Manage API compatibility, wire compatibility, and versioning for NuGet packages and distributed systems.

aspire-configuration

Configure Aspire AppHost to emit explicit app config via environment variables; keep app code free of Aspire clients and service discovery.

aspire-integration-testing

Write integration tests using .NET Aspire

aspire-service-defaults

Create a shared ServiceDefaults project for Aspire applications. Centralizes OpenTelemetry, health checks, resilience, and service discovery configuration across all services.

crap-analysis

Analyze code coverage and CRAP (Change Risk Anti-Patterns) scores to identify high-risk code. Use OpenCover format with ReportGenerator for Risk Hotspots showing cyclomatic complexity and untested code paths.

csharp-concurrency-patterns

Choosing the right concurrency abstraction in .NET - from async/await for I/O to Channels for producer/consumer to Akka.NET for stateful entity management. Avoid locks and manual synchronization unless absolutely necessary.

database-performance

Database access patterns for performance. Separate read/write models, avoid N+1 queries, use AsNoTracking, apply row limits, and never do application-side joins. Works with EF Core and Dapper.

dependency-injection-patterns

Organize DI registrations using IServiceCollection extension methods. Group related services into composable Add* methods for clean Program.cs and reusable configuration in tests.

dotnet-devcert-trust

Diagnose and fix .NET HTTPS dev certificate trust issues on Linux. Covers the full certificate lifecycle from generation to system CA bundle inclusion, with distro-specific guidance for Ubuntu, Fedora, Arch, and WSL2.

dotnet-local-tools

Managing local .NET tools with dotnet-tools.json for consistent tooling across development environments and CI/CD pipelines.

dotnet-slopwatch

Use Slopwatch to detect LLM reward hacking in .NET code changes. Run after every code modification to catch disabled tests, suppressed warnings, empty catch blocks, and other shortcuts that mask real problems.

efcore-patterns

Entity Framework Core best practices including NoTracking by default, query splitting for navigation collections, migration management, dedicated migration services, and common pitfalls to avoid.

ilspy-decompile

Understand implementation details of .NET code by decompiling assemblies. Use when you want to see how a .NET API works internally, inspect NuGet package source, view framework implementation, or understand compiled .NET binaries.

mailpit-integration

Test email sending locally using Mailpit with .NET Aspire. Captures all outgoing emails without sending them. View rendered HTML, inspect headers, and verify delivery in integration tests.

marketplace-publishing

Workflow for publishing skills and agents to the dotnet-skills Claude Code marketplace. Covers adding new content, updating plugin.json, validation, and release tagging.

관련 스킬