Skip to content

Search for slnx files when setting solution-relative content root #61305

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

kimsey0
Copy link
Contributor

@kimsey0 kimsey0 commented Apr 3, 2025

Search for slnx files when setting solution-relative content root

  • You've read the Contributor Guide and Code of Conduct.
  • You've included unit or integration tests for your change, where applicable.
  • You've included inline docs for your change, where applicable.
  • There's an open issue for the PR that you are making. If you'd like to propose a new feature or change, please open an issue to discuss the change or find an existing issue.

Fixes #61304

@ghost ghost added the area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions label Apr 3, 2025
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Apr 3, 2025
@kimsey0
Copy link
Contributor Author

kimsey0 commented Jul 18, 2025

I think this implementation is both source and binary backwards compatible (except for the semantic change of now looking for slnx files too), but would like someone to verify that.

For already compiled assemblies:

  • Calls to builder.UseSolutionRelativeContentRoot(string solutionRelativePath, string solutionName = ".sln") now go to builder.UseSolutionRelativeContentRoot(string solutionRelativePath, string solutionName) and keep the old behavior.
  • Calls to builder.UseSolutionRelativeContentRoot(string solutionRelativePath, string applicationBasePath, string solutionName = ".sln") now to builder.UseSolutionRelativeContentRoot(string solutionRelativePath, string applicationBasePath, string solutionName) and keep the old behavior.

For recompiled assemblies:

  • Calls to builder.UseSolutionRelativeContentRoot(string solutionRelativePath, string solutionName = ".sln") that omit the optional parameter now go to builder.UseSolutionRelativeContentRoot(string solutionRelativePath) and get the new support for .slnx files.
  • Calls to builder.UseSolutionRelativeContentRoot(string solutionRelativePath, string applicationBasePath, string solutionName = ".sln") that omit the optional parameter (by passing applicationBasePath as a named parameter) now go to builder.UseSolutionRelativeContentRoot(string solutionRelativePath, string applicationBasePath, ReadOnlySpan<string> solutionNames = default) and get the new support for .slnx files.

@kimsey0
Copy link
Contributor Author

kimsey0 commented Jul 18, 2025

Tests are failing for two reasons:

  1. I put the new changes in PublicAPI.Shipped.txt. I'm not sure if this should be in ASP.NET Core 9 or 10, thus Shipped or Unshipped? (Or am I misunderstanding something?)
  2. UseSolutionRelativeContentRoot doesn't currently handle permission errors while climbing the folder hierarchy. I think it would make sense to just have it stop if it encounters an UnauthorizedAccessException, but that is technically an unrelated change. Is it fine to add here?

@kimsey0 kimsey0 marked this pull request as ready for review July 18, 2025 22:22
@Copilot Copilot AI review requested due to automatic review settings July 18, 2025 22:22
@kimsey0 kimsey0 requested review from tdykstra, a team and halter73 as code owners July 18, 2025 22:22
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds support for searching .slnx files (XML-based solution files) when setting solution-relative content roots in the TestHost library. The functionality extends the existing UseSolutionRelativeContentRoot methods to support both traditional .sln and newer .slnx solution files by default.

Key changes:

  • Updated the UseSolutionRelativeContentRoot method to search for both .sln and .slnx files by default
  • Refactored the API to accept multiple solution file patterns via ReadOnlySpan
  • Added comprehensive test coverage for .slnx file support

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
WebHostBuilderExtensions.cs Modified UseSolutionRelativeContentRoot methods to support multiple solution file patterns and added .slnx support
PublicAPI.Shipped.txt Updated public API surface to reflect the new method signatures
UseSolutionRelativeContentRootTests.cs Added comprehensive test coverage for .slnx file detection and multiple solution file scenarios
WebApplicationFactorySlnxTests.cs Added integration tests verifying .slnx support works with WebApplicationFactory

this IWebHostBuilder builder,
string solutionRelativePath,
string applicationBasePath,
ReadOnlySpan<string> solutionNames = default)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this use IReadOnlyCollection<string> or similar instead? It's not exactly performance sensitive.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ReadOnlySpan<string> is fine, but we can discuss this in the API proposal issue.

var directoryInfo = new DirectoryInfo(applicationBasePath);
do
{
var solutionPath = Directory.EnumerateFiles(directoryInfo.FullName, solutionName).FirstOrDefault();
if (solutionPath != null)
foreach (var solutionName in solutionNames)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a LINQ query instead if you prefer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the foreach is fine.

@slang25
Copy link
Contributor

slang25 commented Jul 21, 2025

@danroth27 not sure if this is on the roadmap for aspnetcore 10, but it would be great to get in and allow easy migration of sln → slnx.

Even though this is a feature independent from dotnet and aspnetcore, I predict a lot of folks will adopt this around the .NET 10 time frame, so having the migration work seamlessly will be valuable to many customers.

@danmoseley
Copy link
Member

@halter73 do you have cycles to review this? slnx support does seem valuable for .NET 10

@dotnet-policy-service dotnet-policy-service bot added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Jul 29, 2025
@kimsey0 kimsey0 closed this Jul 29, 2025
@kimsey0 kimsey0 reopened this Jul 29, 2025
@dotnet-policy-service dotnet-policy-service bot added this to the 10.0-rc1 milestone Jul 29, 2025
Copy link
Member

@halter73 halter73 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! Can you please open an API Proposal issue for this change? Thanks again.

static Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.UseSolutionRelativeContentRoot(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder, string! solutionRelativePath) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder!
static Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.UseSolutionRelativeContentRoot(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder, string! solutionRelativePath, string! applicationBasePath, string! solutionName) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder!
static Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.UseSolutionRelativeContentRoot(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder, string! solutionRelativePath, string! applicationBasePath, System.ReadOnlySpan<string!> solutionNames = default(System.ReadOnlySpan<string!>)) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder!
static Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.UseSolutionRelativeContentRoot(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder, string! solutionRelativePath, string! solutionName) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder!
Copy link
Member

@halter73 halter73 Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes should go in the PublicAPI.Unshipped.txt file. For the cases where you removed the default value for the optional solutionName parameter, you can prefix the old API with *REMOVED* and then re-add it without the default value. You can take a look at the OpenApi PublicAPI.Unshipped.txt for reference.

@@ -144,19 +158,45 @@ public static IWebHostBuilder UseSolutionRelativeContentRoot(
this IWebHostBuilder builder,
string solutionRelativePath,
string applicationBasePath,
string solutionName = "*.sln")
string solutionName)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I think we can remove the [SuppressMessage(... from this overload now too.

It's really unfortunate that we have all these overloads of just string parameters and we decided to add applicationBasePath to the middle of the parameter list instead of the end. That's what RS0026 was warning about. It makes it very difficult to make backwards compatible changes, but I think this change is safe. I'm glad the new overload at least takes a type other than string.

/// <param name="applicationBasePath">The root of the app's directory.</param>
/// <param name="solutionNames">The names of the solution files to make the content root relative to. If empty, defaults to *.sln and *.slnx.</param>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]

Is this still necessary considering it's now the only overload of UseSolutionRelativeContentRoot that has a default parameter?

this IWebHostBuilder builder,
string solutionRelativePath,
string applicationBasePath,
ReadOnlySpan<string> solutionNames = default)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ReadOnlySpan<string> is fine, but we can discuss this in the API proposal issue.

var directoryInfo = new DirectoryInfo(applicationBasePath);
do
{
var solutionPath = Directory.EnumerateFiles(directoryInfo.FullName, solutionName).FirstOrDefault();
if (solutionPath != null)
foreach (var solutionName in solutionNames)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the foreach is fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions community-contribution Indicates that the PR has been added by a community member pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun
Projects
None yet
Development

Successfully merging this pull request may close these issues.

WebApplicationFactory doesn't set solution relative content root if you switch to slnx solution
6 participants