Summarize with AI:
SLNX files are here! In this post, we'll talk about what this format is, why it exists, what it gets right and where you might get tripped up.
If you’ve worked with .NET for any length of time, you’ve made peace with the .sln file. Not because it’s good (it isn’t) but because it’s the format we have. It’s verbose, GUID-laden and a reliable source of merge conflicts on Friday afternoons. It’s the format we tolerate, not the one we’d choose.
The good news is there’s a new format in town. The .slnx format is Microsoft’s XML-based replacement for the venerable .sln file, and it has been steadily gaining first-class support across Visual Studio, the .NET CLI, MSBuild and Rider over the last year. As of .NET 9.0.200 and Visual Studio 17.13+, you can use it for real projects without crossing your fingers. And as of .NET 10, dotnet new sln defaults to .slnx.
In this post, let’s talk about what .slnx is, why it exists, what it gets right and where you might get tripped up.
To appreciate why we’re getting a new format, let’s remember what we’ve been working with. Here’s a typical fragment of an .sln file.
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.10.34804.81
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyApi", "MyApi\MyApi.csproj", "{F95781B3-A973-4D19-9585-974DA143E6A1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8FC526EA-218B-4615-8410-4E1850611F38}"
ProjectSection(SolutionItems) = preProject
Directory.Build.props = Directory.Build.props
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F95781B3-A973-4D19-9585-974DA143E6A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F95781B3-A973-4D19-9585-974DA143E6A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F95781B3-A973-4D19-9585-974DA143E6A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F95781B3-A973-4D19-9585-974DA143E6A1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
Oof. The SLN format is a custom Microsoft format that has some weird quirks. Every project gets a GUID. Solution folders are themselves projects with their own GUIDs. Configuration combinations even get cross-multiplied so a four-project solution with two configurations and two platforms produces 16 lines of nearly identical setup.
The pain points are well known to us all. If even two developers add a project at the same time, both edit GlobalSection(ProjectConfigurationPlatforms), and Git throws its hands up.
GUIDs, bless their hearts, are not for human eyes. It’s a nightmare trying to figure out which project owns which configuration block. And outside of Visual Studio, generating or modifying .sln files reliably often requires a tool like slngen because no one wants to write the parser themselves.
This is what the .slnx tries to fix.
Here’s the same project, but expressed as .slnx.
<Solution>
<Folder Name="/Solution Items/">
<File Path="Directory.Build.props" />
</Folder>
<Project Path="MyApi/MyApi.csproj" />
</Solution>
Notice how we have no GUIDs, no cross-multiplied configuration table and no EndGlobalSection markers. Even a developer who is foreign to the .slnx format can read it and immediately understand it.
Expanding a little, here’s an example from a layered solution.
<Solution>
<Folder Name="/Solution Items/">
<File Path=".editorconfig" />
<File Path="Directory.Build.props" />
<File Path="Directory.Packages.props" />
</Folder>
<Folder Name="/src/">
<Project Path="src/Application/Application.csproj" />
<Project Path="src/Domain/Domain.csproj" />
<Project Path="src/Infrastructure/Infrastructure.csproj" />
<Project Path="src/Web.Api/Web.Api.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/UnitTests/UnitTests.csproj" />
<Project Path="tests/IntegrationTests/IntegrationTests.csproj" />
</Folder>
</Solution>
If you compare this to the equivalent .sln, you’re looking at maybe a third of the lines, with none of the GUID overhead. If you need to override configurations for a specific project (like, say your test project shouldn’t build in Release mode) you can express that with a Configurations element on the project. In reality, most projects won’t need it at all. If you do, you can review examples in microsoft/vs-solutionpersistence and its samples wiki.
If the .slnx looks similar to a .csproj, that’s intentional. Microsoft’s reasoning is that XML is already the format the rest of MSBuild speaks, the team already had a parser for it, and features like comments and attributes are first-class citizens. A JSON or YAML format might have been trendier, but it would have forced the team to invent semantics for things .csproj already has solved problems for.
Existing projects are a different conversation. Here’s what you get by migrating, and how to do it cleanly.
.sln unless it’s an emergency. With .slnx, opening it in your editor of choice is perfectly reasonable. I’ve fixed broken solution layouts in a text editor faster than Visual Studio would have let me click through the dialogs.Based on the official dotnet sln reference, the actual conversion is one command.
dotnet sln migrate
If you run this in a directory with an .sln file, you’ll get an .slnx file beside it. From Visual Studio, you can also use File -> Save Solution As… and pick XML Solution File (.slnx) from the dropdown. Rider supports a similar flow.
You don’t want to keep both files in the repository. The dotnet sln commands don’t reliably pick the right one when you have both, and it’s easy for the two to get out of sync as people add or remove projects. Pick when to cut over, delete the old file and move on. While there is a community tool called dotnet-sln-sync that helps, that should only be used temporarily and not long term.
As you convert over, here’s a quick checklist to avoid any headaches:
global.json allows the right SDK. You need at least .NET 9.0.200 for full CLI support. If your global.json pins to something older, the migrate command won’t work..slnx..sln, update the path. Wildcard references like **/*.sln will miss the new file. If you are using templates where you have a mix of .sln and .slnx, a pattern like **/*.sln* will be useful.slngen don’t yet support .slnx at the time of this post (you can track the issue here). If you’re reliant on a tool outside of the Microsoft umbrella, check before you migrate.The .slnx format is no longer “preview” in any practical sense, but the ecosystem around it is still catching up.
.slnx file doesn’t open Visual Studio by default. You can fix this with a file association or just open it from inside the IDE.dotnet.defaultSolution to the path of your .slnx if it isn’t auto-detected.<Project Path="src/**/*.csproj" /> and have it discover projects automatically. The team’s reasoning, captured in the globbing feature request, is that globbing slows down solution loading on large repos because Visual Studio has to scan the file system before it can render anything. That makes sense, but I think plenty of us would happily trade a slower cold load for never having to add another project entry by hand..sln directly needs to update. Hopefully the open-source parser library accelerates that, but you’ll find holdouts.None of these are dealbreakers for new projects. For older codebases with a lot of tooling baked in, it’s worth doing a small pilot before flipping the whole repo.
The .slnx format isn’t a revolutionary feature. It doesn’t change how you write code, it doesn’t add new build capabilities, and it doesn’t speed up your application. It is, however, one of those quality-of-life upgrades that quietly results in fewer merge conflicts, less time staring at GUIDs, less friction in your CI pipelines once they’re set up.
If you’re starting a new .NET solution, just use .slnx from day one. And with .NET 10, you’ll get it without lifting a finger. If you’re maintaining an existing .sln file, plan a migration when you have a slow week, audit your tooling and cut over cleanly. The format isn’t going anywhere, and getting ahead of the transition is easier than waiting for some downstream tool to force your hand.
Thanks for reading, and happy coding!
Dave Brock is a software engineer, writer, speaker, open-source contributor and former Microsoft MVP. With a focus on Microsoft technologies, Dave enjoys advocating for modern and sustainable cloud-based solutions.