C# Soluções e Vários Projetos
Um programa de verdade raramente é um projeto só. A lojinha da Ana vai ter o programa de console, uma parte com as regras do estoque (que um dia também vai servir a um site), e os testes. Cada parte é um projeto, com o seu .csproj. Para juntar tudo e compilar de uma vez, o .NET usa uma solução: um arquivo que lista os projetos que andam juntos. Nesta aula você vai criar uma solução no formato novo do .NET 10, o .slnx, pôr dois projetos nela e fazer um usar o outro.
- O que é uma solução e para que ela serve.
- A criar uma com
dotnet new slne a ler o arquivo.slnx.NET 10. - A organizar os projetos na pasta
srce a pô-los na solução comdotnet sln add,listeremove. - A compilar tudo com um
dotnet builde a rodar um projeto comdotnet run --project. - A fazer um projeto usar o outro com
dotnet reference add. - O formato antigo
.sln, odotnet sln migratee os errosMSB1011eMSB4006.
Todas as saídas desta aula são reais, feitas com o .NET 10 SDK (10.0.112) na máquina fictícia da Ana, na pasta ~/projetos/Loja. Você precisa já conhecer Projetos e O Arquivo .csproj.
A ideia: o fichário e os cadernos
Na escola, a Ana tinha um caderno para cada matéria e um fichário que juntava todos. O fichário não tem conteúdo próprio: ele só diz quais cadernos fazem parte do ano. Para levar o ano inteiro para casa, ela pega o fichário.
| Na escola | No .NET | Arquivo |
|---|---|---|
| Um caderno | Um projeto: um programa ou uma biblioteca | Loja.App.csproj |
| O fichário | A solução: a lista dos projetos | Loja.slnx |
| Levar o fichário para casa | dotnet build na pasta da solução compila todos os projetos |
Solução, em inglês, é solution, e por isso a extensão .sln (a antiga) e .slnx (a nova, com o x de XML). Editores como o Visual Studio e o Rider abrem a solução para mostrar todos os projetos de uma vez; o VS Code, com a extensão C# Dev Kit, também.
1. Criar a solução: dotnet new sln
A Ana cria a pasta da lojinha e, dentro dela, a solução:
$ mkdir Loja && cd Loja $ dotnet new sln O modelo "Arquivo de Solução" foi criado com êxito. $ ls Loja.slnx $ cat Loja.slnx <Solution> </Solution>
- dotnet new sln
- Cria uma solução a partir do modelo "Arquivo de Solução". O nome vem da pasta (
Loja); para outro, use-n Nome. - Loja.slnx
- No .NET 10, o formato padrão é o
.slnx.NET 10. Até o .NET 9, o mesmo comando criava um.sln(seção 6). - <Solution> </Solution>
- Uma solução vazia: ainda não há nenhum projeto no fichário. O
.slnxé XML, como o.csproj.
2. Criar os projetos na pasta src
O costume é guardar os projetos numa pasta src (de source, "código-fonte"), e os testes numa pasta tests (veja Testes). Os nomes dos projetos começam com o nome da solução e um ponto: Loja.App (o programa) e Loja.Estoque (as regras do estoque, uma biblioteca, veja Bibliotecas de Classes):
$ dotnet new console -o src/Loja.App O modelo "Aplicativo do Console" foi criado com êxito. Processando ações pós-criação... Restaurando /home/ana/projetos/Loja/src/Loja.App/Loja.App.csproj: Determinando os projetos a serem restaurados... /home/ana/projetos/Loja/src/Loja.App/Loja.App.csproj restaurado (em 109 ms). A restauração foi bem-sucedida. $ dotnet new classlib -o src/Loja.Estoque O modelo "Biblioteca de Classes" foi criado com êxito. Processando ações pós-criação... Restaurando /home/ana/projetos/Loja/src/Loja.Estoque/Loja.Estoque.csproj: Determinando os projetos a serem restaurados... /home/ana/projetos/Loja/src/Loja.Estoque/Loja.Estoque.csproj restaurado (em 111 ms). A restauração foi bem-sucedida.
O -o src/Loja.App cria as duas pastas de uma vez, e o nome do projeto vem da última (Loja.App). O modelo classlib ("biblioteca de classes") cria um projeto sem Program.cs, com um arquivo de exemplo Class1.cs. Por enquanto, os dois projetos só estão na mesma pasta: a solução ainda não sabe deles.
3. Pôr os projetos na solução: dotnet sln add
$ dotnet sln add src/Loja.App src/Loja.Estoque
O projeto ‘src/Loja.App/Loja.App.csproj’ foi adicionado à solução.
O projeto ‘src/Loja.Estoque/Loja.Estoque.csproj’ foi adicionado à solução.
$ cat Loja.slnx
<Solution>
<Folder Name="/src/">
<Project Path="src/Loja.App/Loja.App.csproj" />
<Project Path="src/Loja.Estoque/Loja.Estoque.csproj" />
</Folder>
</Solution>
$ dotnet sln list
Projetos
--------
src/Loja.App/Loja.App.csproj
src/Loja.Estoque/Loja.Estoque.csproj
- dotnet sln add src/Loja.App src/Loja.Estoque
- "Solução, acrescente": põe os projetos na solução da pasta atual. Pode ser a pasta do projeto (o
dotnetacha o.csprojdentro dela) ou o caminho do próprio.csproj. Vários de uma vez, separados por espaço. - <Folder Name="/src/">
- Uma "pasta da solução": um grupo que os editores mostram como uma pastinha. O
dotnet sln addcriou o grupo/src/porque os projetos estão na pastasrc. - <Project Path="…" />
- Um projeto da solução: só o caminho do
.csproj, a partir da pasta da solução. É tudo o que o.slnxprecisa guardar. - dotnet sln list
- Lista os projetos da solução.
A estrutura de pastas da lojinha ficou assim:
Loja/
├── Loja.slnx « a solução (o fichário)
└── src/
├── Loja.App/ « o programa
│ ├── Loja.App.csproj
│ └── Program.cs
└── Loja.Estoque/ « a biblioteca
├── Loja.Estoque.csproj
└── Class1.cs
4. Compilar tudo de uma vez
Na pasta da solução, o dotnet build encontra o Loja.slnx e compila todos os projetos dele:
$ dotnet build
Determinando os projetos a serem restaurados...
Todos os projetos estão atualizados para restauração.
Loja.Estoque -> /home/ana/projetos/Loja/src/Loja.Estoque/bin/Debug/net10.0/Loja.Estoque.dll
Loja.App -> /home/ana/projetos/Loja/src/Loja.App/bin/Debug/net10.0/Loja.App.dll
Compilação com êxito.
0 Aviso(s)
0 Erro(s)
Tempo Decorrido 00:00:08.43
Uma linha Projeto -> …dll para cada projeto. Cada um continua com a sua própria pasta bin. Repare que a biblioteca virou só um .dll (sem executável): ela não roda sozinha. O mesmo vale para dotnet test, dotnet clean e dotnet format: na pasta da solução, eles agem sobre todos os projetos.
Rodar: dotnet run --project
Já o dotnet run precisa saber qual programa rodar:
$ dotnet run Não foi possível localizar um projeto para executar. Verifique se existe um projeto em /home/ana/projetos/Loja ou passe o caminho para o projeto usando --project. $ dotnet run --project src/Loja.App Hello, World!
Com --project, a Ana continua na pasta da solução. A outra forma é entrar na pasta do projeto (cd src/Loja.App) e rodar dotnet run lá.
5. Um projeto usar o outro: dotnet reference add
Agora o conteúdo. A Ana apaga o Class1.cs e escreve a classe Produto na biblioteca:
namespace Loja.Estoque;
public class Produto(string nome, decimal preco, int quantidade)
{
public string Nome { get; } = nome;
public decimal Preco { get; } = preco;
public int Quantidade { get; } = quantidade;
public decimal Total => Preco * Quantidade;
}
E o programa usa essa classe:
using Loja.Estoque;
Produto[] produtos =
[
new("Caderno", 12.50m, 10),
new("Lápis", 1.99m, 40),
];
foreach (Produto p in produtos)
{
Console.WriteLine($"{p.Nome,-8} {p.Quantidade,3} x {p.Preco,8:C} = {p.Total,9:C}");
}
$ dotnet build 2>&1 | grep -E "error" | sort -u /home/ana/projetos/Loja/src/Loja.App/Program.cs(1,7): error CS0246: O nome do tipo ou do namespace "Loja" não pode ser encontrado (está faltando uma diretiva using ou uma referência de assembly?) [/home/ana/projetos/Loja/src/Loja.App/Loja.App.csproj]
Estar na mesma solução não basta. A solução é só o fichário: cada projeto é compilado separado e só enxerga o que está nele mesmo. Para o Loja.App enxergar o Loja.Estoque, é preciso uma referência de projeto:
$ dotnet reference add src/Loja.Estoque --project src/Loja.App
A referência ‘..\Loja.Estoque\Loja.Estoque.csproj’ foi adicionada ao projeto.
$ cat src/Loja.App/Loja.App.csproj
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Loja.Estoque\Loja.Estoque.csproj" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
$ dotnet build
Determinando os projetos a serem restaurados...
/home/ana/projetos/Loja/src/Loja.App/Loja.App.csproj restaurado (em 206 ms).
1 de 2 projetos estão atualizados para restauração.
Loja.Estoque -> /home/ana/projetos/Loja/src/Loja.Estoque/bin/Debug/net10.0/Loja.Estoque.dll
Loja.App -> /home/ana/projetos/Loja/src/Loja.App/bin/Debug/net10.0/Loja.App.dll
Compilação com êxito.
0 Aviso(s)
0 Erro(s)
Tempo Decorrido 00:00:02.12
$ dotnet run --project src/Loja.App
Caderno 10 x R$ 12,50 = R$ 125,00
Lápis 40 x R$ 1,99 = R$ 79,60
- dotnet reference add src/Loja.Estoque
- "Referência, acrescente": o projeto a ser usado.
- --project src/Loja.App
- O projeto que vai usar. Leia assim: "o
Loja.Apppassa a usar oLoja.Estoque". (A ordem antiga,dotnet add src/Loja.App reference src/Loja.Estoque, também funciona.) - <ProjectReference Include="..\Loja.Estoque\Loja.Estoque.csproj" />
- A linha que o comando escreveu no
.csprojdoLoja.App. O caminho é a partir da pasta do próprioLoja.App(..= "a pasta de cima"). A barra invertida\é costume do Windows; no Linux odotnetentende igual. O comando pôs oItemGroupantes doPropertyGroup: a ordem não importa. - Loja.Estoque -> … antes de Loja.App -> …
- O
dotnetdescobre a ordem sozinho: quem é usado é compilado primeiro.
A classe da biblioteca precisa ser public para o outro projeto enxergá-la (veja Modificadores de Acesso). Os detalhes de bibliotecas (o que fica visível, como organizar) estão em Bibliotecas de Classes.
No editor do guia não há solução nem projetos separados: os arquivos das abas são compilados juntos, como se fossem um projeto só. Mas o namespace e o using funcionam igual:
Missão: a aba Produto.cs está no namespace Loja.Estoque, e o Program.cs não o importou: rode e veja o CS0246. Acrescente a linha que falta no começo do Program.cs e rode de novo.
Produto[] produtos =
[
new("Caderno", 12.50m, 10),
new("Lápis", 1.99m, 40),
];
foreach (Produto p in produtos)
{
Console.WriteLine($"{p.Nome,-8} {p.Quantidade,3} x {p.Preco,8:C} = {p.Total,9:C}");
}
namespace Loja.Estoque;
public class Produto(string nome, decimal preco, int quantidade)
{
public string Nome { get; } = nome;
public decimal Preco { get; } = preco;
public int Quantidade { get; } = quantidade;
public decimal Total => Preco * Quantidade;
}
using Loja.Estoque;
Produto[] produtos =
[
new("Caderno", 12.50m, 10),
new("Lápis", 1.99m, 40),
];
foreach (Produto p in produtos)
{
Console.WriteLine($"{p.Nome,-8} {p.Quantidade,3} x {p.Preco,8:C} = {p.Total,9:C}");
}
6. O formato antigo: .sln
Quase todo projeto feito antes do .NET 10, e quase todo tutorial, usa o formato .sln. Para ver como ele é, a Ana criou um com --format sln:
$ dotnet new sln --format sln -n Antiga O modelo "Arquivo de Solução" foi criado com êxito. $ cat Antiga.sln Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal
São 14 linhas (a primeira, em branco) para uma solução vazia, num formato próprio do Visual Studio. Depois de dotnet sln Antiga.sln add src/Loja.Estoque, com um projeto, o arquivo foi para 39 linhas. Um pedaço:
$ cat Antiga.sln
«… as 5 primeiras linhas, iguais …»
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Loja.Estoque", "src\Loja.Estoque\Loja.Estoque.csproj", "{EB621F30-53E9-43A3-9021-E43CBC4574F9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
«…»
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EB621F30-53E9-43A3-9021-E43CBC4574F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EB621F30-53E9-43A3-9021-E43CBC4574F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
«… mais 10 linhas como essas …»
EndGlobal
$ wc -l Antiga.sln
39 Antiga.sln
Cada projeto ganha um código de identificação longo (um GUID, como {EB621F30-…}), repetido em uma dúzia de linhas de configuração. Era difícil de ler e de juntar quando duas pessoas mexiam no mesmo arquivo. O .slnx guarda a mesma informação em XML simples, e foi por isso que virou o padrão. (Para seguir a aula, a Ana apagou essa Antiga.sln e criou outra vazia, logo abaixo.)
.sln (antigo) | .slnx (novo) .NET 10 | |
|---|---|---|
| Formato | Texto próprio do Visual Studio, com GUIDs | XML simples |
| Solução vazia | 14 linhas | 2 linhas |
| Com um projeto | 39 linhas | 5 linhas |
Padrão do dotnet new sln | até o .NET 9 | no .NET 10 |
| Quem abre | Todos os editores e versões do .NET | Só as versões recentes do dotnet (a partir do SDK 9.0.200) e dos editores |
Converter: dotnet sln migrate
Para passar um .sln para o formato novo (aqui, uma Antiga.sln vazia, criada de novo com dotnet new sln --format sln -n Antiga):
$ dotnet sln Antiga.sln add src/Loja.App O projeto ‘src/Loja.App/Loja.App.csproj’ foi adicionado à solução. O projeto ‘src/Loja.Estoque/Loja.Estoque.csproj’ foi adicionado à solução. $ dotnet sln Antiga.sln migrate Arquivo .slnx /home/ana/projetos/Loja/Antiga.slnx gerado. $ ls Antiga.sln Antiga.slnx Loja.slnx src
- dotnet sln Antiga.sln add …
- Com duas soluções na pasta, diga qual logo depois do
sln. Repare: a Ana pediu só oLoja.App, e oLoja.Estoqueveio junto. É que oLoja.Appusa oLoja.Estoque(a referência da seção 5), e odotnet sln addacrescenta também os projetos usados. - dotnet sln Antiga.sln migrate
- "Migre": cria o
Antiga.slnxao lado. O.slnoriginal não é apagado: confira o novo e apague o velho você mesmo.
A Ana apagou as duas (rm Antiga.sln Antiga.slnx), porque a lojinha já tem a Loja.slnx. Uma pasta deve ter uma solução só (veja "Quando dá errado").
7. Tirar um projeto: dotnet sln remove
$ dotnet sln remove src/Loja.Estoque O projeto `src/Loja.Estoque/Loja.Estoque.csproj` foi removido da solução. $ dotnet sln list Projetos -------- src/Loja.App/Loja.App.csproj
O remove só tira o projeto da lista: a pasta src/Loja.Estoque e os arquivos continuam no disco. A Ana pôs de volta com dotnet sln add src/Loja.Estoque.
Quando dá errado
error MSB1011: duas soluções na mesma pasta
$ ls Antiga.sln Loja.slnx src $ dotnet build MSBUILD : error MSB1011: Especifique o arquivo de solução ou de projeto a usar porque esta pasta contém mais de um arquivo de solução ou de projeto.
O que aconteceu: com duas soluções (ou uma solução e um .csproj) na pasta, o dotnet não sabe qual usar. Acontece muito logo depois de um migrate.
Como resolver: diga qual (dotnet build Loja.slnx) ou, melhor, deixe uma só. Nunca ponha a solução na mesma pasta de um .csproj: a solução fica em cima, os projetos em src/.
Não foi possível encontrar o projeto ou diretório e já contém o projeto
$ dotnet sln add src/Loja.Relatorios Não foi possível encontrar o projeto ou diretório ‘src/Loja.Relatorios’. $ dotnet sln add src/Loja.App A solução /home/ana/projetos/Loja/Loja.slnx já contém o projeto src/Loja.App/Loja.App.csproj.
O que aconteceu: no primeiro, o projeto ainda não existe (ou o caminho está errado). No segundo, nada de errado: o projeto já estava na solução e não foi duplicado.
Como resolver: crie o projeto antes (dotnet new … -o src/Loja.Relatorios) e confira o caminho com ls src.
error CS0246: está na solução, mas o outro projeto não enxerga
O que aconteceu: é o erro da seção 5: O nome do tipo ou do namespace "Loja" não pode ser encontrado. A solução junta os projetos para compilar, mas não faz um usar o outro.
Como resolver: dotnet reference add src/Loja.Estoque --project src/Loja.App, o using Loja.Estoque; no arquivo e a classe public na biblioteca.
error MSB4006: Há uma dependência circular
$ dotnet reference add src/Loja.App --project src/Loja.Estoque
A referência ‘..\Loja.App\Loja.App.csproj’ foi adicionada ao projeto.
$ dotnet build 2>&1 | tail -5
/usr/lib/dotnet/sdk/10.0.112/NuGet.targets(1294,5): error MSB4006: Há uma dependência circular no elemento gráfico de dependência circular de destino envolvendo o destino "_GenerateRestoreProjectPathWalk". [/home/ana/projetos/Loja/src/Loja.Estoque/Loja.Estoque.csproj]
0 Aviso(s)
1 Erro(s)
Tempo Decorrido 00:00:00.95
O que aconteceu: o Loja.App já usava o Loja.Estoque, e a Ana fez o Loja.Estoque usar o Loja.App também. Agora cada um precisa do outro pronto para ser compilado: um círculo sem começo. A tradução está confusa, mas a palavra-chave é "circular" (em inglês, There is a circular dependency).
Como resolver: as referências só podem ir num sentido: o programa usa a biblioteca, nunca o contrário. Tire a referência errada passando o caminho do arquivo .csproj:
$ dotnet reference remove --project src/Loja.Estoque/Loja.Estoque.csproj src/Loja.App/Loja.App.csproj Referência de projeto ‘../Loja.App/Loja.App.csproj’ removida.
Cuidado: com o caminho da pasta (dotnet reference remove src/Loja.App --project src/Loja.Estoque), o SDK 10.0.112 calcula o caminho errado e responde Não foi possível encontrar a referência de projeto ‘../../Loja.App/Loja.App.csproj’, sem remover nada. Se a biblioteca precisar de algo do programa, esse algo deve mudar para a biblioteca.
Referência rápida
| Comando | O que faz |
|---|---|
dotnet new sln | Cria a solução .slnx (o nome vem da pasta; -n Nome para outro). |
dotnet new sln --format sln | Cria no formato antigo .sln. |
dotnet sln add caminho… | Põe projetos na solução (e os que eles usam). |
dotnet sln list | Lista os projetos. |
dotnet sln remove caminho | Tira um projeto da lista (não apaga nada). |
dotnet sln Antiga.sln migrate | Cria um .slnx a partir de um .sln. |
dotnet build (na pasta da solução) | Compila todos os projetos, na ordem certa. |
dotnet run --project src/Loja.App | Roda um projeto da solução. |
dotnet reference add A --project B | O projeto B passa a usar o A. |
dotnet reference list --project B | Lista os projetos que o B usa. |
dotnet reference remove --project B.csproj A.csproj | Tira a referência (use os caminhos dos arquivos). |
Exercícios
1. Criar a solução
Complete o comando que cria a solução na pasta atual:
2. Pôr um projeto
Complete para pôr o projeto src/Loja.Web na solução:
3. Rodar da pasta da solução
Complete para rodar o Loja.App sem sair da pasta da solução:
4. Os projetos Loja.App e Loja.Estoque estão na mesma solução, mas o Loja.App não acha a classe Produto. Por quê?
5. O que tem dentro de um .slnx?
6. A biblioteca Loja.Estoque passou a usar o programa Loja.App, que já usava a biblioteca. O que acontece?
Resumo
- Uma solução é o fichário: a lista dos projetos que andam juntos. Ela não tem código.
dotnet new slncria um.slnx.NET 10: XML simples com<Project Path="…" />. O formato antigo é o.sln;dotnet sln migrateconverte.- Estrutura comum: a solução em cima, os projetos em
src/e os testes emtests/. dotnet sln add,listeremovemexem na lista.- Na pasta da solução,
dotnet buildcompila tudo, na ordem certa; para rodar,dotnet run --project src/Loja.App. - Para um projeto usar outro:
dotnet reference add A --project B, que escreve um<ProjectReference>. As referências vão num sentido só.