Solutions and .slnx
|
This section documents MSBuild 18.9, the .NET SDK 10.0.402 (with its This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The C# Reference bibliography lists the reference material consulted while preparing these pages. |
A solution groups projects so that tools can build, test and open them together. It is not an MSBuild project: MSBuild
translates it into one in memory (a metaproject) and builds every project it lists. Projects do not need a solution — dotnet build src/Ledgerline works on its own, and ProjectReference items define the real dependency graph — but
IDEs open solutions, CI builds them, and the repository root is easier to work with when one file lists everything. This
page uses .NET SDK 10.0.402 and the Ledgerline repository. The introduction is in
Namespaces, Assemblies and Projects — Solutions and global.json.
.sln and .slnx
The .sln format dates from Visual Studio .NET: a line-oriented text file with project-type GUIDs, a GUID per project
and a Global section that maps every solution configuration and platform to every project. It is hard to read and merge.
.slnx is its XML replacement: only paths, solution folders and the configurations that differ from the defaults.
MSBuild builds .slnx since 17.12, and since .NET 10 dotnet new sln creates .slnx (in .NET 9 and earlier it
created .sln). Ledgerline uses it:
<Solution>
<Folder Name="/samples/">
<Project Path="samples/Ledgerline.Report/Ledgerline.Report.csproj" />
</Folder>
<Folder Name="/src/">
<Project Path="src/Ledgerline.Build/Ledgerline.Build.csproj" />
<Project Path="src/Ledgerline.Cli/Ledgerline.Cli.csproj" />
<Project Path="src/Ledgerline.Templates/Ledgerline.Templates.csproj" />
<Project Path="src/Ledgerline/Ledgerline.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/Ledgerline.Tests/Ledgerline.Tests.csproj" />
</Folder>
</Solution>
The same two projects in the classic format, created with dotnet new sln --format sln (only the start of the file):
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ledgerline", "src\Ledgerline\Ledgerline.csproj", "{1375A7E0-9D8E-481E-A5E2-B3BE15F9BECF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ledgerline.Cli", "src\Ledgerline.Cli\Ledgerline.Cli.csproj", "{C8B61180-0FEC-43D6-8183-EB92B53187EB}"
EndProject
Global
…
Check that every tool in the team’s chain opens .slnx before switching (current Visual Studio, Rider and VS Code C# Dev
Kit releases do); scripts and CI templates that parse .sln text themselves need updating.
dotnet sln
dotnet sln [<SOLUTION_FILE>] <command> edits a solution. Without a file argument it uses the single .sln, .slnx
or .slnf in the current folder and fails (or prompts) when there are several.
| Command | What it does |
|---|---|
|
Creates a solution ( |
|
Adds projects. By default, folders in the project path become solution folders ( |
|
Removes projects, by path or by name (without the extension). |
|
Lists the projects (works on |
|
Writes an |
dotnet sln list
Project(s)
----------
samples/Ledgerline.Report/Ledgerline.Report.csproj
src/Ledgerline.Build/Ledgerline.Build.csproj
src/Ledgerline.Cli/Ledgerline.Cli.csproj
src/Ledgerline.Templates/Ledgerline.Templates.csproj
src/Ledgerline/Ledgerline.csproj
tests/Ledgerline.Tests/Ledgerline.Tests.csproj
Migrating an existing .sln is one command; the generated .slnx keeps the folders and records the platforms the old
file declared:
dotnet sln Legacy.sln migrate
cat Legacy.slnx
.slnx file ~/ledgerline/mig/Legacy.slnx generated.
<Solution>
<Configurations>
<Platform Name="Any CPU" />
<Platform Name="x64" />
<Platform Name="x86" />
</Configurations>
<Folder Name="/src/">
<Project Path="../src/Ledgerline.Cli/Ledgerline.Cli.csproj" />
<Project Path="../src/Ledgerline/Ledgerline.csproj" />
</Folder>
</Solution>
After migrating, delete the .sln (or keep it only while some tool still needs it): two solution files in the root
make every bare dotnet build fail, see MSB1011: more than one project or solution file.
The noun-first commands that edit projects (dotnet reference add, dotnet package add) are on
Solutions, References and Packages.
Solution filters (.slnf)
A solution filter is a JSON file that names a solution and a subset of its projects. Visual Studio loads only those projects, and MSBuild builds only those, which keeps large repositories fast to open:
{
"solution": {
"path": "Ledgerline.slnx",
"projects": [
"src/Ledgerline/Ledgerline.csproj",
"tests/Ledgerline.Tests/Ledgerline.Tests.csproj"
]
}
}
dotnet sln Ledgerline.Libraries.slnf list
dotnet build Ledgerline.Libraries.slnf -tl:off -v:m
Project(s)
----------
src/Ledgerline/Ledgerline.csproj
tests/Ledgerline.Tests/Ledgerline.Tests.csproj
Ledgerline -> ~/ledgerline/src/Ledgerline/bin/Debug/net10.0/Ledgerline.dll
Ledgerline -> ~/ledgerline/src/Ledgerline/bin/Debug/net8.0/Ledgerline.dll
Ledgerline -> ~/ledgerline/src/Ledgerline/bin/Debug/netstandard2.0/Ledgerline.dll
Ledgerline.Tests -> ~/ledgerline/tests/Ledgerline.Tests/bin/Debug/net10.0/Ledgerline.Tests.dll
Build succeeded.
Visual Studio writes the paths with backslashes, which JSON requires to be doubled (src\\Ledgerline\\Ledgerline.csproj);
forward slashes, as above, work on every platform. MSBuild "automatically follows dependencies": a referenced project
that is not in the filter is still built through the ProjectReference. CLI support is partial on the baseline: dotnet sln list reads .slnf since SDK 9.0.3xx, but
creating a filter (dotnet new slnf) and adding or removing projects in one (dotnet sln <file>.slnf add|remove) arrive
only in .NET 11, which is a preview; with SDK 10.0.402 the file is written by hand or by Visual Studio.
How MSBuild builds a solution
dotnet build Ledgerline.slnx (or msbuild Ledgerline.slnx) parses the solution, generates an in-memory metaproject
with one MSBuild task call per project and the requested target (Build, Clean, Restore, Pack…), and builds it.
Directory.Build.props and Directory.Build.targets are not imported into the metaproject; the solution-level
equivalents are:
-
Directory.Solution.propsandDirectory.Solution.targets, found by walking up from the solution file; -
before.<solution>.sln.targets/after.<solution>.sln.targetsnext to the solution (for.slnx, thebefore.<solution>.slnx.targetsnames work since MSBuild 17.14); -
ImportDirectorySolutionProps/ImportDirectorySolutionTargetsto switch the lookup off andDirectorySolutionPropsPath/DirectorySolutionTargetsPathto point elsewhere.
<Project>
<Target Name="LedgerlineSolutionSummary" AfterTargets="Build">
<Message Importance="high" Text="Solution $(SolutionName) built in configuration $(Configuration)." />
</Target>
</Project>
dotnet build Ledgerline.slnx -tl:off -v:m | grep Solution
dotnet build Ledgerline.Libraries.slnf -tl:off -v:m | grep Solution
Solution Ledgerline built in configuration Debug.
Solution Ledgerline built in configuration Debug.
The filter build runs the same metaproject (with fewer projects), so $(SolutionName) is the underlying solution’s.
These hooks apply only to command-line builds; Visual Studio does not build a metaproject. The documentation
therefore recommends putting customisation in Directory.Build.props/.targets, which every project imports wherever
it is built from.
Source: MSBuild: Customize solution builds.
MSB1011: more than one project or solution file
When dotnet build (or msbuild) gets no file argument, it looks in the current folder for exactly one project or
solution file. Leaving an old .sln next to the new .slnx is the usual cause of:
With Legacy.sln created next to Ledgerline.slnx:
dotnet build -tl:off
MSBUILD : error MSB1011: Specify which project or solution file to use because this folder contains more than one project or solution file.
Name the file (dotnet build Ledgerline.slnx) or remove the extra one. The rules have exceptions: a solution next to a
project of the same name, or next to several projects, is chosen without an error; a solution next to a single project
with a different name is an error. Behaviour beyond the documented cases is easy to trip over — with a Journals.csproj
and a batching.proj in one folder, MSBuild 18.9 silently picked batching.proj — so name the file in scripts.
Source: MSBuild error MSB1011.
Cross-tool equivalents
| Task | Equivalents |
|---|---|
Create a solution |
|
Convert |
|
Build a subset |
|
Solution-level hook |
|
Common mistakes
-
Two solution files (or a solution and a project) in the same folder —
MSB1011for every argument-less command. -
Expecting
Directory.Build.propsto affect the solution build — it affects the projects, not the metaproject; useDirectory.Solution.props. -
Relying on solution-level build order or dependencies — they only exist in solution builds; declare
ProjectReferenceitems instead, so a single project builds correctly too. -
Parsing
.slntext in scripts — breaks after migrating; usedotnet sln list.
Release notes
-
MSBuild 17.12:
.slnxsolutions build from the command line. -
SDK 9.0.3xx:
dotnet sln listaccepts.slnf. -
MSBuild 17.14:
before.<name>.slnx.targets/after.<name>.slnx.targets. -
.NET 10:
dotnet new slncreates.slnxby default. -
.NET 11 (preview):
dotnet new slnf;dotnet sln add/removeon solution filters.