Skip to content

Commit dcec533

Browse files
authored
Search for slnx files when setting solution-relative content root (#61305)
1 parent 09ad011 commit dcec533

File tree

4 files changed

+329
-9
lines changed

4 files changed

+329
-9
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
11
#nullable enable
2+
*REMOVED*static Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.UseSolutionRelativeContentRoot(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder, string! solutionRelativePath, string! applicationBasePath, string! solutionName = "*.sln") -> Microsoft.AspNetCore.Hosting.IWebHostBuilder!
3+
*REMOVED*static Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.UseSolutionRelativeContentRoot(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder, string! solutionRelativePath, string! solutionName = "*.sln") -> Microsoft.AspNetCore.Hosting.IWebHostBuilder!
4+
static Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.UseSolutionRelativeContentRoot(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder, string! solutionRelativePath) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder!
5+
static Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.UseSolutionRelativeContentRoot(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder, string! solutionRelativePath, string! applicationBasePath, string! solutionName) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder!
6+
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!
7+
static Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.UseSolutionRelativeContentRoot(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder, string! solutionRelativePath, string! solutionName) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder!
8+

src/Hosting/TestHost/src/WebHostBuilderExtensions.cs

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ namespace Microsoft.AspNetCore.TestHost;
1616
/// </summary>
1717
public static class WebHostBuilderExtensions
1818
{
19+
private static readonly string[] _defaultSolutionNames = ["*.sln", "*.slnx"];
20+
1921
/// <summary>
2022
/// Enables the <see cref="TestServer" /> service.
2123
/// </summary>
@@ -117,20 +119,32 @@ public static IWebHostBuilder ConfigureTestContainer<TContainer>(this IWebHostBu
117119
return webHostBuilder;
118120
}
119121

122+
/// <summary>
123+
/// Sets the content root of relative to the <paramref name="solutionRelativePath" />.
124+
/// </summary>
125+
/// <param name="builder">The <see cref="IWebHostBuilder"/>.</param>
126+
/// <param name="solutionRelativePath">The directory of the solution file.</param>
127+
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
128+
public static IWebHostBuilder UseSolutionRelativeContentRoot(
129+
this IWebHostBuilder builder,
130+
string solutionRelativePath)
131+
{
132+
return builder.UseSolutionRelativeContentRoot(solutionRelativePath, AppContext.BaseDirectory, _defaultSolutionNames);
133+
}
134+
120135
/// <summary>
121136
/// Sets the content root of relative to the <paramref name="solutionRelativePath" />.
122137
/// </summary>
123138
/// <param name="builder">The <see cref="IWebHostBuilder"/>.</param>
124139
/// <param name="solutionRelativePath">The directory of the solution file.</param>
125140
/// <param name="solutionName">The name of the solution file to make the content root relative to.</param>
126141
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
127-
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
128142
public static IWebHostBuilder UseSolutionRelativeContentRoot(
129143
this IWebHostBuilder builder,
130144
string solutionRelativePath,
131-
string solutionName = "*.sln")
145+
string solutionName)
132146
{
133-
return builder.UseSolutionRelativeContentRoot(solutionRelativePath, AppContext.BaseDirectory, solutionName);
147+
return builder.UseSolutionRelativeContentRoot(solutionRelativePath, AppContext.BaseDirectory, [solutionName]);
134148
}
135149

136150
/// <summary>
@@ -141,24 +155,49 @@ public static IWebHostBuilder UseSolutionRelativeContentRoot(
141155
/// <param name="applicationBasePath">The root of the app's directory.</param>
142156
/// <param name="solutionName">The name of the solution file to make the content root relative to.</param>
143157
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
144-
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
145158
public static IWebHostBuilder UseSolutionRelativeContentRoot(
146159
this IWebHostBuilder builder,
147160
string solutionRelativePath,
148161
string applicationBasePath,
149-
string solutionName = "*.sln")
162+
string solutionName)
163+
{
164+
return builder.UseSolutionRelativeContentRoot(solutionRelativePath, applicationBasePath, [solutionName]);
165+
}
166+
167+
/// <summary>
168+
/// Sets the content root of relative to the <paramref name="solutionRelativePath" />.
169+
/// </summary>
170+
/// <param name="builder">The <see cref="IWebHostBuilder"/>.</param>
171+
/// <param name="solutionRelativePath">The directory of the solution file.</param>
172+
/// <param name="applicationBasePath">The root of the app's directory.</param>
173+
/// <param name="solutionNames">The names of the solution files to make the content root relative to. If empty, defaults to *.sln and *.slnx.</param>
174+
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
175+
[SuppressMessage("ApiDesign", "RS0027:Public API with optional parameter(s) should have the most parameters amongst its public overloads", Justification = "Required to maintain compatibility")]
176+
public static IWebHostBuilder UseSolutionRelativeContentRoot(
177+
this IWebHostBuilder builder,
178+
string solutionRelativePath,
179+
string applicationBasePath,
180+
ReadOnlySpan<string> solutionNames = default)
150181
{
151182
ArgumentNullException.ThrowIfNull(solutionRelativePath);
152183
ArgumentNullException.ThrowIfNull(applicationBasePath);
153184

185+
if (solutionNames.IsEmpty)
186+
{
187+
solutionNames = _defaultSolutionNames;
188+
}
189+
154190
var directoryInfo = new DirectoryInfo(applicationBasePath);
155191
do
156192
{
157-
var solutionPath = Directory.EnumerateFiles(directoryInfo.FullName, solutionName).FirstOrDefault();
158-
if (solutionPath != null)
193+
foreach (var solutionName in solutionNames)
159194
{
160-
builder.UseContentRoot(Path.GetFullPath(Path.Combine(directoryInfo.FullName, solutionRelativePath)));
161-
return builder;
195+
var solutionPath = Directory.EnumerateFiles(directoryInfo.FullName, solutionName).FirstOrDefault();
196+
if (solutionPath != null)
197+
{
198+
builder.UseContentRoot(Path.GetFullPath(Path.Combine(directoryInfo.FullName, solutionRelativePath)));
199+
return builder;
200+
}
162201
}
163202

164203
directoryInfo = directoryInfo.Parent;
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.AspNetCore.Hosting;
5+
using Microsoft.Extensions.DependencyInjection;
6+
7+
namespace Microsoft.AspNetCore.TestHost;
8+
9+
#pragma warning disable ASPDEPR004 // WebHostBuilder is obsolete
10+
#pragma warning disable ASPDEPR008 // WebHost is obsolete
11+
public class UseSolutionRelativeContentRootTests : IDisposable
12+
{
13+
private readonly string _tempDirectory;
14+
private readonly string _contentDirectory;
15+
16+
public UseSolutionRelativeContentRootTests()
17+
{
18+
_tempDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")[..8]);
19+
_contentDirectory = Path.Combine(_tempDirectory, "src");
20+
Directory.CreateDirectory(_contentDirectory);
21+
}
22+
23+
[Fact]
24+
public void UseSolutionRelativeContentRoot_FindsSlnFile()
25+
{
26+
var solutionFile = Path.Combine(_tempDirectory, "TestApp.sln");
27+
File.WriteAllText(solutionFile, "Microsoft Visual Studio Solution File, Format Version 12.00");
28+
29+
var builder = new WebHostBuilder()
30+
.UseTestServer()
31+
.Configure(app => { });
32+
33+
builder.UseSolutionRelativeContentRoot("src", applicationBasePath: _tempDirectory);
34+
35+
using var host = builder.Build();
36+
var environment = host.Services.GetRequiredService<IWebHostEnvironment>();
37+
38+
Assert.Equal(_contentDirectory, environment.ContentRootPath);
39+
}
40+
41+
[Fact]
42+
public void UseSolutionRelativeContentRoot_FindsSlnxFile()
43+
{
44+
var solutionFile = Path.Combine(_tempDirectory, "TestApp.slnx");
45+
File.WriteAllText(solutionFile, """
46+
<Solution>
47+
<Configurations>
48+
<Configuration Name="Debug|Any CPU" />
49+
<Configuration Name="Release|Any CPU" />
50+
</Configurations>
51+
</Solution>
52+
""");
53+
54+
var builder = new WebHostBuilder()
55+
.UseTestServer()
56+
.Configure(app => { });
57+
58+
builder.UseSolutionRelativeContentRoot("src", applicationBasePath: _tempDirectory);
59+
60+
using var host = builder.Build();
61+
var environment = host.Services.GetRequiredService<IWebHostEnvironment>();
62+
63+
Assert.Equal(_contentDirectory, environment.ContentRootPath);
64+
}
65+
66+
[Fact]
67+
public void UseSolutionRelativeContentRoot_WithSolutionName_FindsSpecifiedFile()
68+
{
69+
var subDirectory = Path.Combine(_tempDirectory, "sub");
70+
Directory.CreateDirectory(subDirectory);
71+
72+
var slnFile = Path.Combine(subDirectory, "TestApp.sln");
73+
var slnxFile = Path.Combine(_tempDirectory, "TestApp.slnx");
74+
File.WriteAllText(slnFile, "Microsoft Visual Studio Solution File, Format Version 12.00");
75+
File.WriteAllText(slnxFile, """
76+
<Solution>
77+
<Configurations>
78+
<Configuration Name="Debug|Any CPU" />
79+
</Configurations>
80+
</Solution>
81+
""");
82+
83+
var builder = new WebHostBuilder()
84+
.UseTestServer()
85+
.Configure(app => { });
86+
87+
builder.UseSolutionRelativeContentRoot("src", _tempDirectory, "*.slnx");
88+
89+
using var host = builder.Build();
90+
var environment = host.Services.GetRequiredService<IWebHostEnvironment>();
91+
92+
Assert.Equal(_contentDirectory, environment.ContentRootPath);
93+
}
94+
95+
[Fact]
96+
public void UseSolutionRelativeContentRoot_WithMultipleSolutionNames_FindsInCurrentDirectoryFirst()
97+
{
98+
var expectedPath = Path.Combine(_contentDirectory, "sub");
99+
Directory.CreateDirectory(expectedPath);
100+
101+
var slnFile = Path.Combine(_tempDirectory, "TestApp.sln");
102+
var slnxFile = Path.Combine(_contentDirectory, "TestApp.slnx");
103+
File.WriteAllText(slnFile, "Microsoft Visual Studio Solution File, Format Version 12.00");
104+
File.WriteAllText(slnxFile, """
105+
<Solution>
106+
<Configurations>
107+
<Configuration Name="Debug|Any CPU" />
108+
</Configurations>
109+
</Solution>
110+
""");
111+
112+
var builder = new WebHostBuilder()
113+
.UseTestServer()
114+
.Configure(app => { });
115+
116+
builder.UseSolutionRelativeContentRoot("sub", _contentDirectory, ["*.sln", "*.slnx"]);
117+
118+
using var host = builder.Build();
119+
var environment = host.Services.GetRequiredService<IWebHostEnvironment>();
120+
121+
Assert.Equal(expectedPath, environment.ContentRootPath);
122+
}
123+
124+
[Fact]
125+
public void UseSolutionRelativeContentRoot_WithMultipleSolutionNames_WorksWithMultipleFiles()
126+
{
127+
var slnFile = Path.Combine(_tempDirectory, "TestApp.sln");
128+
var slnxFile = Path.Combine(_tempDirectory, "TestApp.slnx");
129+
File.WriteAllText(slnFile, "Microsoft Visual Studio Solution File, Format Version 12.00");
130+
File.WriteAllText(slnxFile, """
131+
<Solution>
132+
<Configurations>
133+
<Configuration Name="Debug|Any CPU" />
134+
</Configurations>
135+
</Solution>
136+
""");
137+
138+
var builder = new WebHostBuilder()
139+
.UseTestServer()
140+
.Configure(app => { });
141+
142+
builder.UseSolutionRelativeContentRoot("src", applicationBasePath: _tempDirectory, solutionNames: ["*.sln", "*.slnx"]);
143+
144+
using var host = builder.Build();
145+
var environment = host.Services.GetRequiredService<IWebHostEnvironment>();
146+
147+
Assert.Equal(_contentDirectory, environment.ContentRootPath);
148+
}
149+
150+
[Fact]
151+
public void UseSolutionRelativeContentRoot_ThrowsWhenSolutionNotFound()
152+
{
153+
var builder = new WebHostBuilder()
154+
.UseTestServer()
155+
.Configure(app => { });
156+
157+
var exception = Assert.Throws<InvalidOperationException>(() =>
158+
builder.UseSolutionRelativeContentRoot("src", applicationBasePath: _tempDirectory));
159+
160+
Assert.Contains("Solution root could not be located", exception.Message);
161+
Assert.Contains(_tempDirectory, exception.Message);
162+
}
163+
164+
[Fact]
165+
public void UseSolutionRelativeContentRoot_WithSolutionName_SearchesParentDirectories()
166+
{
167+
var subDirectory = Path.Combine(_tempDirectory, "sub", "folder");
168+
Directory.CreateDirectory(subDirectory);
169+
170+
var solutionFile = Path.Combine(_tempDirectory, "TestApp.slnx");
171+
File.WriteAllText(solutionFile, """
172+
<Solution>
173+
<Configurations>
174+
<Configuration Name="Debug|Any CPU" />
175+
</Configurations>
176+
</Solution>
177+
""");
178+
179+
var builder = new WebHostBuilder()
180+
.UseTestServer()
181+
.Configure(app => { });
182+
183+
builder.UseSolutionRelativeContentRoot("src", subDirectory, "*.slnx");
184+
185+
using var host = builder.Build();
186+
var environment = host.Services.GetRequiredService<IWebHostEnvironment>();
187+
188+
Assert.Equal(_contentDirectory, environment.ContentRootPath);
189+
}
190+
191+
public void Dispose()
192+
{
193+
if (Directory.Exists(_tempDirectory))
194+
{
195+
Directory.Delete(_tempDirectory, recursive: true);
196+
}
197+
}
198+
}
199+
#pragma warning restore ASPDEPR008 // WebHost is obsolete
200+
#pragma warning disable ASPDEPR004 // WebHostBuilder is obsolete
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.AspNetCore.Hosting;
5+
using Microsoft.AspNetCore.Mvc.Testing;
6+
using Microsoft.AspNetCore.TestHost;
7+
using Microsoft.Extensions.DependencyInjection;
8+
using BasicWebSite;
9+
10+
namespace Microsoft.AspNetCore.Mvc.FunctionalTests;
11+
12+
public class WebApplicationFactorySlnxTests : IClassFixture<WebApplicationFactory<BasicWebSite.Startup>>, IDisposable
13+
{
14+
private readonly string _tempDirectory;
15+
private readonly string _contentDirectory;
16+
17+
public WebApplicationFactorySlnxTests(WebApplicationFactory<BasicWebSite.Startup> factory)
18+
{
19+
Factory = factory;
20+
_tempDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")[..8]);
21+
_contentDirectory = Path.Combine(_tempDirectory, "BasicWebSite");
22+
23+
Directory.CreateDirectory(_tempDirectory);
24+
Directory.CreateDirectory(_contentDirectory);
25+
26+
// Create a minimal wwwroot directory to satisfy content root expectations
27+
var wwwrootDir = Path.Combine(_contentDirectory, "wwwroot");
28+
Directory.CreateDirectory(wwwrootDir);
29+
}
30+
31+
public WebApplicationFactory<BasicWebSite.Startup> Factory { get; }
32+
33+
[Fact]
34+
public async Task WebApplicationFactory_UsesSlnxForSolutionRelativeContentRoot()
35+
{
36+
// Create .slnx file in temp directory
37+
var slnxFile = Path.Combine(_tempDirectory, "TestSolution.slnx");
38+
File.WriteAllText(slnxFile, """
39+
<Solution>
40+
<Configurations>
41+
<Configuration Name="Debug|Any CPU" />
42+
<Configuration Name="Release|Any CPU" />
43+
</Configurations>
44+
<Folder Name="/BasicWebSite/">
45+
<Project Path="BasicWebSite/BasicWebSite.csproj" />
46+
</Folder>
47+
</Solution>
48+
""");
49+
50+
var factory = Factory.WithWebHostBuilder(builder =>
51+
{
52+
builder.UseSolutionRelativeContentRoot("BasicWebSite", _tempDirectory, "TestSolution.slnx");
53+
});
54+
55+
using var client = factory.CreateClient();
56+
57+
// Verify that the content root was set correctly by accessing the environment
58+
var environment = factory.Services.GetRequiredService<IWebHostEnvironment>();
59+
Assert.Equal(_contentDirectory, environment.ContentRootPath);
60+
Assert.True(Directory.Exists(environment.ContentRootPath));
61+
62+
// Verify the factory is functional with the .slnx-resolved content root
63+
var response = await client.GetAsync("/");
64+
Assert.True(response.IsSuccessStatusCode);
65+
}
66+
67+
public void Dispose()
68+
{
69+
if (Directory.Exists(_tempDirectory))
70+
{
71+
Directory.Delete(_tempDirectory, recursive: true);
72+
}
73+
}
74+
}

0 commit comments

Comments
 (0)