Skip to content

[release/9.0] Add .slnx fallback for TestHost content root discovery#64953

Merged
wtgodbe merged 2 commits intorelease/9.0from
copilot/update-webhostbuilderextensions-behavior
Jan 13, 2026
Merged

[release/9.0] Add .slnx fallback for TestHost content root discovery#64953
wtgodbe merged 2 commits intorelease/9.0from
copilot/update-webhostbuilderextensions-behavior

Conversation

Copy link
Contributor

Copilot AI commented Jan 6, 2026

Add .slnx fallback for TestHost content root discovery

Description

This pull request adds .slnx fallback support to the test-only UseSolutionRelativeContentRoot method in the Microsoft.AspNetCore.TestHost. When searching for solution files with the *.sln pattern, the method now falls back to searching for .slnx files if no .sln file is found, preventing the InvalidOperationException that would previously occur before test developers can easily call UseSolutionRelativeContentRoot themselves.

This has already been fixed in .NET 10 by #61305, but that included public API changes, so this is not a direct backport. Unlike, the .NET 10 change that treats .slnx and .sln equivalently by default, this change will only fall back to looking for an *.slnx file if the test server would otherwise throw an InvalidOperationException due to not being able to find an ".sln" file.

Fixes #61304 in .NET 9.

Customer Impact

#61304 has gotten a lot of attention, because it is a pain point for migrating solution from .sln to .slnx.

System.InvalidOperationException : Solution root could not be located using application root C:\Project\Path\bin\Debug\net9.0\

Stack Trace:
 at Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.UseSolutionRelativeContentRoot(IWebHostBuilder builder, String solutionRelativePath, String applicationBasePath, String solutionName)
 at Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.UseSolutionRelativeContentRoot(IWebHostBuilder builder, String solutionRelativePath, String solutionName)
 at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.SetContentRoot(IWebHostBuilder builder)
 at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.<ConfigureHostBuilder>b__22_0(IWebHostBuilder webHostBuilder)
 at Microsoft.Extensions.Hosting.GenericHostWebHostBuilderExtensions.ConfigureWebHost(IHostBuilder builder, Func`3 createWebHostBuilder, Action`1 configure, Action`1 configureWebHostBuilder)
 at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.ConfigureHostBuilder(IHostBuilder hostBuilder)
 at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.EnsureServer()
 at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateDefaultClient(DelegatingHandler[] handlers)
 at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateDefaultClient(Uri baseAddress, DelegatingHandler[] handlers)
 at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateClient(WebApplicationFactoryClientOptions options)
 at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateClient()

Test projects that worked fine with an .sln-based solution throw after migrating to *.slnx before developers even get access to the IWebHostBuilder, so they don't get the opportunity to easily manually reconfigure the content root by calling UseSolutionRelativeContentRoot with "*.slnx". Instead, the current general workaround is fragile and unintuitive:

public static class MvcTestingAppManifestHelper
{
    private const string ManifestFileName = "MvcTestingAppManifest.json";
    private static readonly JsonSerializerOptions JsonSerializerOptions = new() { WriteIndented = true };

    public static void AddAssemblyToManifest(Assembly assembly)
    {
        if (!File.Exists(ManifestFileName))
        {
            return;
        }

        // The manifest file is a dictionary of Assembly.FullName to ContentRoot.
        var manifest = JsonSerializer.Deserialize<Dictionary<string, string>>(File.ReadAllText(ManifestFileName))!;

        // Internally, the tilde is used to translate the content root to the AppContext.BaseDirectory.
        if (manifest.TryAdd(assembly.FullName!, "~"))
        {
            File.WriteAllText(ManifestFileName, JsonSerializer.Serialize(manifest, JsonSerializerOptions));
        }
    }
}

#61304 (comment)

Regression?

  • Yes
  • No

Not technically, but you could consider it a regression in behavior when migrating from .sln to .slnx solutions.

Risk

  • High
  • Medium
  • Low

This change only has an impact if starting the test server would otherwise throw an InvalidOperationException early during initialization due to not being able to find an ".sln" file.

Verification

  • Manual (required)
  • Automated

Packaging changes reviewed?

  • Yes
  • No
  • N/A

  • Add non-breaking .slnx fallback for content root lookup.

Description

Problem: UseSolutionRelativeContentRoot threw when only a .slnx existed, and .slnx could shadow .sln in parents.
Changes:

  • Keep .sln precedence; if none found, retry search for .slnx without changing API.
  • Added focused tests validating .sln preference and .slnx fallback discovery.

Example:

new WebHostBuilder()
    .UseSolutionRelativeContentRoot("src/MyApp", appBasePath, "*.sln"); // now falls back to slnx if no sln
Original prompt

Create a variant of #61305 that contains no breaking API changes. It should also avoid the behavioral breaking change when you could technically have a .slnx file that then "shadows" a .sln file in a parent folder. The only behavioral change is that it should not throw the InvalidOperationException from https://github.com/dotnet/aspnetcore/blob/release/9.0/src/Hosting/TestHost/src/WebHostBuilderExtensions.cs#L166 if there was an "*.slnx" file.


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI changed the title [WIP] Update WebHostBuilderExtensions to avoid exception with .slnx file Add .slnx fallback for TestHost content root discovery Jan 6, 2026
Copilot AI requested a review from halter73 January 6, 2026 23:40
@halter73 halter73 marked this pull request as ready for review January 8, 2026 16:49
Copilot AI review requested due to automatic review settings January 8, 2026 16:49
Copy link
Contributor

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 pull request adds non-breaking .slnx fallback support to the UseSolutionRelativeContentRoot method in TestHost. When searching for solution files with the "*.sln" pattern, the method now falls back to searching for .slnx files if no .sln file is found, preventing the InvalidOperationException that would previously occur.

Key changes:

  • Added .slnx fallback logic that only activates when no .sln file is found and the search pattern is "*.sln"
  • Maintained .sln precedence to avoid breaking changes where .slnx files could shadow .sln files in parent directories
  • Added comprehensive tests validating both the fallback behavior and .sln precedence

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/Hosting/TestHost/src/WebHostBuilderExtensions.cs Added conditional .slnx fallback logic after .sln search fails, maintaining backward compatibility
src/Hosting/TestHost/test/WebHostBuilderExtensionsTests.cs New test file with two tests: one verifying .slnx fallback and one verifying .sln takes precedence over .slnx

@halter73
Copy link
Member

halter73 commented Jan 9, 2026

/backport to release/8.0

@github-actions
Copy link
Contributor

github-actions bot commented Jan 9, 2026

Started backporting to release/8.0 (link to workflow run)

@halter73 halter73 changed the title Add .slnx fallback for TestHost content root discovery [release/9.0] Add .slnx fallback for TestHost content root discovery Jan 10, 2026
@halter73 halter73 added the Servicing-consider Shiproom approval is required for the issue label Jan 10, 2026
@rbhanda rbhanda added Servicing-approved Shiproom has approved the issue and removed Servicing-consider Shiproom approval is required for the issue labels Jan 12, 2026
@wtgodbe wtgodbe merged commit 0e42d40 into release/9.0 Jan 13, 2026
30 of 31 checks passed
@wtgodbe wtgodbe deleted the copilot/update-webhostbuilderextensions-behavior branch January 13, 2026 00:13
@dotnet-policy-service dotnet-policy-service bot modified the milestones: 9.0.x, 9.0.13 Jan 13, 2026
github-actions bot pushed a commit to saan800/github that referenced this pull request Feb 24, 2026
Updated
[Microsoft.AspNetCore.Mvc.Testing](https://github.com/dotnet/aspnetcore)
from 9.0.12 to 9.0.13.

<details>
<summary>Release notes</summary>

_Sourced from [Microsoft.AspNetCore.Mvc.Testing's
releases](https://github.com/dotnet/aspnetcore/releases)._

## 9.0.13

[Release](https://github.com/dotnet/core/releases/tag/v9.0.13)

## What's Changed
* Update branding to 9.0.13 by @​vseanreesermsft in
dotnet/aspnetcore#64938
* [release/9.0] (deps): Bump src/submodules/googletest from `1b96fa1` to
`9156d4c` by @​dependabot[bot] in
dotnet/aspnetcore#64908
* [release/9.0] Pass container image correctly for source-build job in
official build by @​akoeplinger in
dotnet/aspnetcore#64781
* [release/9.0] Update dependencies from dotnet/extensions by
@​dotnet-maestro[bot] in dotnet/aspnetcore#64686
* [release/9.0] Update dependencies from dotnet/arcade by
@​dotnet-maestro[bot] in dotnet/aspnetcore#64685
* [release/9.0] Update gradle by @​github-actions[bot] in
dotnet/aspnetcore#64980
* Backport #​64657: Use shallow clones for CI jobs by @​Copilot in
dotnet/aspnetcore#64677
* [release/9.0] Update dependencies from dotnet/extensions by
@​dotnet-maestro[bot] in dotnet/aspnetcore#65012
* [release/9.0] Add .slnx fallback for TestHost content root discovery
by @​Copilot in dotnet/aspnetcore#64953
* Merging internal commits for release/9.0 by @​vseanreesermsft in
dotnet/aspnetcore#65046


**Full Changelog**:
dotnet/aspnetcore@v9.0.12...v9.0.13)

Commits viewable in [compare
view](dotnet/aspnetcore@v9.0.12...v9.0.13).
</details>

Updated
[Microsoft.AspNetCore.OpenApi](https://github.com/dotnet/aspnetcore)
from 9.0.12 to 9.0.13.

<details>
<summary>Release notes</summary>

_Sourced from [Microsoft.AspNetCore.OpenApi's
releases](https://github.com/dotnet/aspnetcore/releases)._

## 9.0.13

[Release](https://github.com/dotnet/core/releases/tag/v9.0.13)

## What's Changed
* Update branding to 9.0.13 by @​vseanreesermsft in
dotnet/aspnetcore#64938
* [release/9.0] (deps): Bump src/submodules/googletest from `1b96fa1` to
`9156d4c` by @​dependabot[bot] in
dotnet/aspnetcore#64908
* [release/9.0] Pass container image correctly for source-build job in
official build by @​akoeplinger in
dotnet/aspnetcore#64781
* [release/9.0] Update dependencies from dotnet/extensions by
@​dotnet-maestro[bot] in dotnet/aspnetcore#64686
* [release/9.0] Update dependencies from dotnet/arcade by
@​dotnet-maestro[bot] in dotnet/aspnetcore#64685
* [release/9.0] Update gradle by @​github-actions[bot] in
dotnet/aspnetcore#64980
* Backport #​64657: Use shallow clones for CI jobs by @​Copilot in
dotnet/aspnetcore#64677
* [release/9.0] Update dependencies from dotnet/extensions by
@​dotnet-maestro[bot] in dotnet/aspnetcore#65012
* [release/9.0] Add .slnx fallback for TestHost content root discovery
by @​Copilot in dotnet/aspnetcore#64953
* Merging internal commits for release/9.0 by @​vseanreesermsft in
dotnet/aspnetcore#65046


**Full Changelog**:
dotnet/aspnetcore@v9.0.12...v9.0.13)

Commits viewable in [compare
view](dotnet/aspnetcore@v9.0.12...v9.0.13).
</details>

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
renebentes pushed a commit to renebentes/3054 that referenced this pull request Feb 25, 2026
Updated
[Microsoft.AspNetCore.OpenApi](https://github.com/dotnet/aspnetcore)
from 9.0.9 to 9.0.13.

<details>
<summary>Release notes</summary>

_Sourced from [Microsoft.AspNetCore.OpenApi's
releases](https://github.com/dotnet/aspnetcore/releases)._

## 9.0.13

[Release](https://github.com/dotnet/core/releases/tag/v9.0.13)

## What's Changed
* Update branding to 9.0.13 by @​vseanreesermsft in
dotnet/aspnetcore#64938
* [release/9.0] (deps): Bump src/submodules/googletest from `1b96fa1` to
`9156d4c` by @​dependabot[bot] in
dotnet/aspnetcore#64908
* [release/9.0] Pass container image correctly for source-build job in
official build by @​akoeplinger in
dotnet/aspnetcore#64781
* [release/9.0] Update dependencies from dotnet/extensions by
@​dotnet-maestro[bot] in dotnet/aspnetcore#64686
* [release/9.0] Update dependencies from dotnet/arcade by
@​dotnet-maestro[bot] in dotnet/aspnetcore#64685
* [release/9.0] Update gradle by @​github-actions[bot] in
dotnet/aspnetcore#64980
* Backport #​64657: Use shallow clones for CI jobs by @​Copilot in
dotnet/aspnetcore#64677
* [release/9.0] Update dependencies from dotnet/extensions by
@​dotnet-maestro[bot] in dotnet/aspnetcore#65012
* [release/9.0] Add .slnx fallback for TestHost content root discovery
by @​Copilot in dotnet/aspnetcore#64953
* Merging internal commits for release/9.0 by @​vseanreesermsft in
dotnet/aspnetcore#65046


**Full Changelog**:
dotnet/aspnetcore@v9.0.12...v9.0.13)

## 9.0.12

[Release](https://github.com/dotnet/core/releases/tag/v9.0.12)

## What's Changed
* Update branding to 9.0.12 by @​vseanreesermsft in
dotnet/aspnetcore#64248
* Update `Microsoft.Build` versions to 17.8.43 by @​MackinnonBuck in
dotnet/aspnetcore#64277
* [release/9.0] (deps): Bump src/submodules/googletest from `9706f75` to
`6ec14df` by @​dependabot[bot] in
dotnet/aspnetcore#64230
* [release/9.0] Update dependencies from dotnet/extensions by
@​dotnet-maestro[bot] in dotnet/aspnetcore#64111
* [release/9.0] Update dependencies from dotnet/arcade by
@​dotnet-maestro[bot] in dotnet/aspnetcore#64065
* [release/9.0] Upgrade to MacOS 15 for CI by @​wtgodbe in
dotnet/aspnetcore#64310
* Merging internal commits for release/9.0 by @​vseanreesermsft in
dotnet/aspnetcore#64312
* [release/9.0] Don't use netcoreapp2.1 in dotnet-get-document by
@​wtgodbe in dotnet/aspnetcore#64351
* [release/9.0] AppContext for HttpSys CBT hardening by @​BrennanConroy
in dotnet/aspnetcore#64297
* [release/9.0] Update dependencies from dotnet/arcade by
@​dotnet-maestro[bot] in dotnet/aspnetcore#64350
* [release/9.0] Update dependencies from dotnet/extensions by
@​dotnet-maestro[bot] in dotnet/aspnetcore#64388
* [release/9.0] Delete signalr-daily-tests.yml by @​github-actions[bot]
in dotnet/aspnetcore#64589
* [release/9.0] (deps): Bump src/submodules/googletest from `6ec14df` to
`1b96fa1` by @​dependabot[bot] in
dotnet/aspnetcore#64580
* [release/9.0] Update dependencies from dotnet/extensions by
@​dotnet-maestro[bot] in dotnet/aspnetcore#64503


**Full Changelog**:
dotnet/aspnetcore@v9.0.11...v9.0.12

## 9.0.11

[Release](https://github.com/dotnet/core/releases/tag/v9.0.11)

## What's Changed
* Update branding to 9.0.11 by @​vseanreesermsft in
dotnet/aspnetcore#63950
* [release/9.0] Update dependencies from dotnet/extensions by
@​dotnet-maestro[bot] in dotnet/aspnetcore#63677
* [release/9.0] Update dependencies from dotnet/arcade by
@​dotnet-maestro[bot] in dotnet/aspnetcore#63678
* [release/9.0] (deps): Bump src/submodules/googletest from `eb2d85e` to
`9706f75` by @​dependabot[bot] in
dotnet/aspnetcore#63894
* [release/9.0] Fixed devtools url used for debug with chrome and edge
by @​github-actions[bot] in
dotnet/aspnetcore#61948
* [release/9.0] (http2): Lower WINDOWS_UPDATE received on (half)closed
stream to stream abortion by @​DeagleGross in
dotnet/aspnetcore#63934
* [release/9.0] Re-quarantine
ServerRoutingTest.NavigationLock_OverlappingNavigationsCancelExistingNavigations_HistoryNavigation
by @​github-actions[bot] in
dotnet/aspnetcore#63956
* [release/9.0] Fix nginx install on mac, linux by @​wtgodbe in
dotnet/aspnetcore#63966
* [Hot Reload] Do not attempt to apply empty deltas. by @​tmat in
dotnet/aspnetcore#63979
* Merging internal commits for release/9.0 by @​vseanreesermsft in
dotnet/aspnetcore#64036
* Revert log level severity for unknown proxy in
ForwardedHeadersMiddleware by @​BrennanConroy in
dotnet/aspnetcore#64091
* Set timeoutInMinutes to 0 for Windows build job by @​vseanreesermsft
in dotnet/aspnetcore#64126


**Full Changelog**:
dotnet/aspnetcore@v9.0.10...v9.0.11

## 9.0.10

[Release](https://github.com/dotnet/core/releases/tag/v9.0.10)

## What's Changed
* Update branding to 9.0.10 by @​vseanreesermsft in
dotnet/aspnetcore#63510
* [9.0] Make duplicate deb/rpm packages so we can sign them with the new
PMC key by @​jkoritzinsky in
dotnet/aspnetcore#63249
* [release/9.0] Extend Unofficial 1ES template in IdentityModel nightly
tests job by @​github-actions[bot] in
dotnet/aspnetcore#63465
* [release/9.0] (deps): Bump src/submodules/googletest from `373af2e` to
`eb2d85e` by @​dependabot[bot] in
dotnet/aspnetcore#63501
* [release/9.0] Quarantine ResponseBody_WriteContentLength_PassedThrough
by @​wtgodbe in dotnet/aspnetcore#63533
* [release/9.0] Update dependencies from dotnet/arcade by
@​dotnet-maestro[bot] in dotnet/aspnetcore#63304
* [release/9.0] [OpenAPI] Use invariant culture for TextWriter by
@​martincostello in dotnet/aspnetcore#62239
* [release/9.0] Update dependencies from dotnet/extensions by
@​dotnet-maestro[bot] in dotnet/aspnetcore#63303
* Unquarantine `RadioButtonGetsResetAfterSubmittingEnhancedForm` by
@​ilonatommy in dotnet/aspnetcore#63556
* [release/9.0] Update dependencies from dotnet/extensions by
@​dotnet-maestro[bot] in dotnet/aspnetcore#63577
* Merging internal commits for release/9.0 by @​vseanreesermsft in
dotnet/aspnetcore#63604
* [release/9.0] Update dependencies from dotnet/arcade by
@​dotnet-maestro[bot] in dotnet/aspnetcore#63648
* backport(9.0): Fix runtime architecture detection logic in ANCM. by
@​DeagleGross in dotnet/aspnetcore#63707


**Full Changelog**:
dotnet/aspnetcore@v9.0.9...v9.0.10

Commits viewable in [compare
view](dotnet/aspnetcore@v9.0.9...v9.0.13).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Microsoft.AspNetCore.OpenApi&package-manager=nuget&previous-version=9.0.9&new-version=9.0.13)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Servicing-approved Shiproom has approved the issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants