From sln to slnx in Minutes: Cleaner Diffs, Faster Merges

Solution files got an upgrade in .NET 10. This guide explains what .slnx is, why it replaces .sln, and how to migrate using the CLI, Visual Studio, or Rider. Learn how it reduces merge conflicts, what changes in CI and Docker, and how to roll it out to a team with confidence.

If you have ever cracked open a .sln file and felt like you were reading ancient runes, welcome to the club. Solution GUIDs, nested GUIDs, configuration GUIDs. So many GUIDs I started hearing them in my sleep. With .NET 10, solution files finally learned to speak plain XML with .slnx. Fewer conflicts, cleaner diffs, and files you can actually read without summoning an archaeologist.

What changed in .NET 10

New solutions created with the .NET 10 SDK use .slnx by default. You can still opt into the old .sln when you need it, but the benefits are practical:

  • Human readable XML that mirrors your folders
  • Predictable diffs that keep Git happy
  • Alignment with modern SDK-style project files

A tiny before and after

Here is a small two-project solution as .slnx. It reads like your Solution Explorer tree.

<Solution>
<Folder Name="src">
<Project Path="src/Falcon.Api/Falcon.Api.csproj" />
<Project Path="src/Falcon.Core/Falcon.Core.csproj" />
</Folder>
</Solution>

No project type GUIDs. No NestedProjects section. No configuration mapping per project. Just a straight list of what belongs where.

Create new solutions with .slnx

You do not have to do anything special. The default is already .slnx.

Terminal window
dotnet new sln -n MillenniumFalcon

Need the legacy format for an old toolchain for a bit longer?

Terminal window
dotnet new sln -n MillenniumFalcon --format sln

Migrate an existing solution in minutes

The CLI is the fastest route. Run this from the folder that contains your current .sln.

Terminal window
dotnet sln MyShip.sln migrate

Got only one solution in the folder? You can let the CLI find it.

Terminal window
dotnet sln migrate

What you get: a new MyShip.slnx beside your original .sln. Build and test with the new file, then delete the old one to avoid confusion.

Visual Studio path:

  • File > Save Solution As
  • Choose Xml Solution File (*.slnx)

Rider path:

  • Right click solution > Save As .slnx

Tip: you need .NET SDK 9.0.200 or newer for the migrate command. Check your version with dotnet --version.

Validate your build and tests

Your build commands do not change. Point them at the .slnx or let the CLI auto-discover the solution.

Terminal window
dotnet build --configuration Release

If you like quick sanity checks, drop a tiny console app in your solution and make sure it still runs.

Before the snippet, a little context: verifying the runtime version is an easy smoke test that confirms you are building and running the same bits you expect.

using System;
Console.WriteLine($"Hello from .NET {Environment.Version}");

And a tiny test also keeps the guardrails in place.

Quick thought: this test proves your test project builds and executes under the migrated solution.

using Xunit;
public class GandalfMath
{
[Fact]
public void YouShallNotDivideByZero()
{
Assert.Throws<DivideByZeroException>(() => { var _ = 42 / int.Parse("0"); });
}
}

Folders and nesting that match your mental model

Solution folders map to XML folders. What you see is what you get.

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

CI, Docker, and the boring stuff that should just work

If your pipeline calls dotnet restore, dotnet build, and dotnet test in the solution directory, it will keep working. The CLI discovers the solution file. The only time you need a change is when the pipeline hardcodes the file name. Update YourApp.sln to YourApp.slnx and you are done.

Dockerfile tip: copy the .slnx instead of the .sln if you cache restores at the solution level.

COPY ["YourApp.slnx", "."]
RUN dotnet restore

Why merge conflicts get less spicy

Picture two developers adding different projects on separate branches. With .sln, you get conflicting edits in three places: project list, configuration mappings, and nested projects. With .slnx, each added project is a single XML line under a folder. Git can usually merge those inserts cleanly. Less time wrestling with conflicts means more time writing code with names like DwightShruteBeetService.

Team rollout checklist

  • Remove the old .sln after you validate the new file
  • Update any hardcoded references in build scripts or Dockerfiles
  • Verify SDK 9.0.200 or newer across all dev machines and runners
  • Add a short note in your README about the switch
  • If you have custom tools that parse .sln, check for updates or plan a short-term workaround

Wrap up

.slnx trades mystery for clarity. You get readable XML, fewer merge conflicts, simpler diffs, and a format that matches modern .NET. Migration is a one-liner, tooling support is broad, and your pipelines keep humming. The only real risk is hanging on to the old format and the merge headaches that come with it.

May your solution trees be tidy, your diffs be small, and your GUIDs stay in retirement.