Estrutura de Projeto e Configuração de Build .NET
Quando Usar Esta Habilidade
Use esta habilidade quando:
- Configurando uma nova solução .NET com as melhores práticas modernas
- Configurando propriedades de build centralizadas em vários projetos
- Implementando gerenciamento central de versões de pacotes
- Configurando SourceLink para depuração e pacotes NuGet
- Automatizando o gerenciamento de versões com notas de lançamento
- Fixando versões do SDK para builds consistentes
Habilidades Relacionadas
dotnet-local-tools- Gerenciamento de ferramentas locais .NET com dotnet-tools.jsonmicrosoft-extensions-configuration- Padrões de validação de configuração
Formato de Arquivo de Solução (.slnx)
O formato .slnx é o formato moderno de arquivo de solução baseado em XML introduzido no .NET 9. Ele substitui o formato tradicional .sln.
Benefícios em Relação ao .sln Tradicional
| Aspecto | .sln (Legado) | .slnx (Moderno) |
|---|---|---|
| Formato | Formato de texto personalizado | XML padrão |
| Legibilidade | GUIDs, sintaxe críptica | Limpo, legível por humanos |
| Controle de versão | Difícil de diff/merge | Fácil de diff/merge |
| Edição | Requer IDE | Qualquer editor de texto |
Requisitos de Versão
| Ferramenta | Versão Mínima |
|---|---|
| .NET SDK | 9.0.200 |
| Visual Studio | 17.13 |
| MSBuild | Visual Studio Build Tools 17.13 |
Nota: A partir do .NET 10, dotnet new sln cria arquivos .slnx por padrão. No .NET 9, você deve migrar explicitamente ou especificar o formato.
Exemplo de Arquivo .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>
Migrando de .sln para .slnx
Use o comando dotnet sln migrate para converter soluções existentes:
# Migrar um arquivo de solução específico
dotnet sln MySolution.sln migrate
# Ou se apenas um .sln existir no diretório, execute:
dotnet sln migrate
Importante: Não mantenha arquivos .sln e .slnx no mesmo repositório. Isso causa problemas com a detecção automática de solução e pode levar a problemas de sincronização. Após a migração, exclua o arquivo .sln antigo.
Você também pode migrar no Visual Studio:
- Abra a solução
- Selecione a Solução no Gerenciador de Soluções
- Vá para Arquivo > Salvar Solução Como...
- Altere "Salvar como tipo" para Arquivo de Solução XML (*.slnx)
Criando uma Nova Solução .slnx
# .NET 10+: Cria .slnx por padrão
dotnet new sln --name MySolution
# .NET 9: Especifique o formato explicitamente
dotnet new sln --name MySolution --format slnx
# Adicionar projetos (funciona da mesma forma para ambos os formatos)
dotnet sln add src/MyApp/MyApp.csproj
Recomendação
Se você estiver usando .NET 9.0.200 ou posterior, migre suas soluções para .slnx. Os benefícios são significativos:
- Muito menos conflitos de merge (sem GUIDs aleatórios mudando)
- Legível e editável em qualquer editor de texto
- Consistente com o formato moderno
.csproj - Melhor experiência de diff/revisão em pull requests
Directory.Build.props
Directory.Build.props fornece configuração de build centralizada que se aplica a todos os projetos em uma árvore de diretórios. Coloque-o na raiz da solução.
Exemplo Completo
<Project>
<!-- Metadados -->
<PropertyGroup>
<Authors>Sua Equipe</Authors>
<Company>Sua Empresa</Company>
<!-- Ano de copyright dinâmico - atualiza automaticamente -->
<Copyright>Copyright © 2020-$([System.DateTime]::Now.Year) Sua Empresa</Copyright>
<Product>Seu Produto</Product>
<PackageProjectUrl>https://github.com/suaorg/seurepo</PackageProjectUrl>
<RepositoryUrl>https://github.com/suaorg/seurepo</RepositoryUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageTags>suas;tags;aqui</PackageTags>
</PropertyGroup>
<!-- Configurações de Linguagem C# -->
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>$(NoWarn);CS1591</NoWarn> <!-- Comentários XML ausentes -->
</PropertyGroup>
<!-- Gerenciamento de Versão -->
<PropertyGroup>
<VersionPrefix>1.0.0</VersionPrefix>
<PackageReleaseNotes>Veja RELEASE_NOTES.md</PackageReleaseNotes>
</PropertyGroup>
<!-- Definições de Framework Alvo (propriedades reutilizáveis) -->
<PropertyGroup>
<NetStandardLibVersion>netstandard2.0</NetStandardLibVersion>
<NetLibVersion>net8.0</NetLibVersion>
<NetTestVersion>net9.0</NetTestVersion>
</PropertyGroup>
<!-- Configuração SourceLink -->
<PropertyGroup>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
</ItemGroup>
<!-- Ativos de Pacote 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>
<!-- Declarações Using Globais -->
<ItemGroup>
<Using Include="System.Collections.Immutable" />
</ItemGroup>
</Project>
Padrões Principais
Ano de Copyright Dinâmico
<Copyright>Copyright © 2020-$([System.DateTime]::Now.Year) Sua Empresa</Copyright>
Usa funções de propriedade do MSBuild para inserir o ano atual no momento do build. Sem atualizações manuais necessárias.
Propriedades de Framework Alvo Reutilizáveis
Defina os frameworks alvo uma vez, referencie em todos os lugares:
<!-- Em Directory.Build.props -->
<PropertyGroup>
<NetLibVersion>net8.0</NetLibVersion>
<NetTestVersion>net9.0</NetTestVersion>
</PropertyGroup>
<!-- Em MyApp.csproj -->
<PropertyGroup>
<TargetFramework>$(NetLibVersion)</TargetFramework>
</PropertyGroup>
<!-- Em MyApp.Tests.csproj -->
<PropertyGroup>
<TargetFramework>$(NetTestVersion)</TargetFramework>
</PropertyGroup>
SourceLink para Pacotes NuGet
SourceLink permite depuração passo a passo de pacotes NuGet:
<PropertyGroup>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup>
<!-- Escolha o provedor certo para seu controle de origem -->
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
<!-- Ou: Microsoft.SourceLink.AzureRepos.Git -->
<!-- Ou: Microsoft.SourceLink.GitLab -->
<!-- Ou: Microsoft.SourceLink.Bitbucket.Git -->
</ItemGroup>
Directory.Packages.props - Gerenciamento Central de Pacotes
O Gerenciamento Central de Pacotes (CPM) fornece uma única fonte de verdade para todas as versões de pacotes NuGet.
Configuração
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<!-- Definir variáveis de versão para pacotes relacionados -->
<PropertyGroup>
<AkkaVersion>1.5.35</AkkaVersion>
<AspireVersion>9.1.0</AspireVersion>
</PropertyGroup>
<!-- Dependências da Aplicação -->
<ItemGroup Label="Dependências da App">
<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>
<!-- Dependências de Build/Ferramentas -->
<ItemGroup Label="Dependências de Build">
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
</ItemGroup>
<!-- Dependências de Teste -->
<ItemGroup Label="Dependências de Teste">
<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>
Consumindo Pacotes (Sem Versão Necessária)
<!-- Em MyApp.csproj -->
<ItemGroup>
<PackageReference Include="Akka" />
<PackageReference Include="Akka.Cluster" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>
<!-- Em MyApp.Tests.csproj -->
<ItemGroup>
<PackageReference Include="xunit" />
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
</ItemGroup>
Benefícios
- Única fonte de verdade - Todas as versões em um arquivo
- Sem desvio de versão - Todos os projetos usam as mesmas versões
- Atualizações fáceis - Altere uma vez, aplica-se em todos os lugares
- Pacotes agrupados - Variáveis de versão para pacotes relacionados (ex.: todos os pacotes Akka)
global.json - Fixação da Versão do SDK
Fixe a versão do SDK .NET para builds consistentes em todos os ambientes.
{
"sdk": {
"version": "9.0.200",
"rollForward": "latestFeature"
}
}
Políticas de Roll Forward
| Política | Comportamento |
|---|---|
disable | Versão exata exigida |
patch | Mesmo major.minor, último patch |
feature | Mesmo major, último minor.patch |
latestFeature | Mesmo major, última feature band |
minor | Mesmo major, último minor |
latestMinor | Mesmo major, último minor |
major | Último SDK (não recomendado) |
Recomendado: latestFeature - Permite atualizações de patch dentro da mesma feature band.
Gerenciamento de Versão com RELEASE_NOTES.md
Formato das Notas de Lançamento
#### 1.2.0 15 de Janeiro de 2025 ####
- Adicionada nova funcionalidade X
- Corrigido bug em Y
- Melhorada performance de Z
#### 1.1.0 10 de Dezembro de 2024 ####
- Lançamento inicial com funcionalidades A, B, C
Script de Análise (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
}
Script de Atualização de Versão (bumpVersion.ps1)
function UpdateVersionAndReleaseNotes {
param (
[Parameter(Mandatory=$true)]
[PSCustomObject]$ReleaseNotesResult,
[Parameter(Mandatory=$true)]
[string]$XmlFilePath
)
$xmlContent = New-Object XML
$xmlContent.Load($XmlFilePath)
# Atualizar VersionPrefix
$versionElement = $xmlContent.SelectSingleNode("//VersionPrefix")
$versionElement.InnerText = $ReleaseNotesResult.Version
# Atualizar PackageReleaseNotes
$notesElement = $xmlContent.SelectSingleNode("//PackageReleaseNotes")
$notesElement.InnerText = $ReleaseNotesResult.ReleaseNotes
$xmlContent.Save($XmlFilePath)
}
Script de Build (build.ps1)
# Carregar scripts auxiliares
. "$PSScriptRoot\scripts\getReleaseNotes.ps1"
. "$PSScriptRoot\scripts\bumpVersion.ps1"
# Analisar notas de lançamento e atualizar 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 "Atualizado para versão $($releaseNotes.Version)"
Integração CI/CD
# Exemplo GitHub Actions
- name: Atualizar versão a partir das notas de lançamento
shell: pwsh
run: ./build.ps1
- name: Build
run: dotnet build -c Release
- name: Empacotar com versão da tag
run: dotnet pack -c Release /p:PackageVersion=${{ github.ref_name }}
- name: Enviar para NuGet
run: dotnet nuget push **/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
NuGet.Config
Configure origens e comportamento do 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" />
<!-- Adicione feeds privados se necessário -->
<!-- <add key="MinhaEmpresa" value="https://pkgs.dev.azure.com/minhaorg/_packaging/meuFeed/nuget/v3/index.json" /> -->
</packageSources>
</configuration>
Principais Configurações:
<clear />- Remove origens herdadas/padrão para builds reproduzíveisdisableSourceControlIntegration- Previne problemas de integração TFS/Git
Estrutura Completa do Projeto
MySolution/
├── .config/
│ └── dotnet-tools.json # Ferramentas locais .NET
├── .github/
│ └── workflows/
│ ├── pr-validation.yml # Verificações de PR
│ └── release.yml # Publicação NuGet
├── scripts/
│ ├── getReleaseNotes.ps1 # Analisar RELEASE_NOTES.md
│ └── bumpVersion.ps1 # Atualizar Directory.Build.props
├── src/
│ ├── MyApp/
│ │ └── MyApp.csproj
│ └── MyApp.Core/
│ └── MyApp.Core.csproj
├── tests/
│ └── MyApp.Tests/
│ └── MyApp.Tests.csproj
├── Directory.Build.props # Configuração de build centralizada
├── Directory.Packages.props # Gerenciamento central de versões de pacotes
├── global.json # Fixação da versão do SDK
├── MySolution.slnx # Arquivo de solução moderno
├── NuGet.Config # Origens e configuração do NuGet
├── RELEASE_NOTES.md # Notas de lançamento (para atualização de versão)
├── README.md # Documentação do projeto
└── logo.png # Ícone do pacote NuGet