Skip to content

Commit 9478bcc

Browse files
committed
Update UseSolutionRelativeContentRoot to support sln and slnx files by default
1 parent 773dd5a commit 9478bcc

File tree

4 files changed

+286
-10
lines changed

4 files changed

+286
-10
lines changed

src/Hosting/TestHost/src/PublicAPI.Shipped.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,10 @@ static Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.ConfigureTestConta
5656
static Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.ConfigureTestServices(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! webHostBuilder, System.Action<Microsoft.Extensions.DependencyInjection.IServiceCollection!>! servicesConfiguration) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder!
5757
static Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.GetTestClient(this Microsoft.AspNetCore.Hosting.IWebHost! host) -> System.Net.Http.HttpClient!
5858
static Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.GetTestServer(this Microsoft.AspNetCore.Hosting.IWebHost! host) -> Microsoft.AspNetCore.TestHost.TestServer!
59-
static Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.UseSolutionRelativeContentRoot(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder, string! solutionRelativePath, string! applicationBasePath, string! solutionName = "*.sln") -> Microsoft.AspNetCore.Hosting.IWebHostBuilder!
60-
static Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.UseSolutionRelativeContentRoot(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder, string! solutionRelativePath, string! solutionName = "*.sln") -> Microsoft.AspNetCore.Hosting.IWebHostBuilder!
59+
static Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.UseSolutionRelativeContentRoot(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder, string! solutionRelativePath) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder!
60+
static Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.UseSolutionRelativeContentRoot(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder, string! solutionRelativePath, string! applicationBasePath, string! solutionName) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder!
61+
static Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.UseSolutionRelativeContentRoot(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder, string! solutionRelativePath, string! applicationBasePath, System.ReadOnlySpan<string!> solutionNames) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder!
62+
static Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.UseSolutionRelativeContentRoot(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder, string! solutionRelativePath, string! solutionName) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder!
6163
static Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.UseTestServer(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder!
6264
static Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.UseTestServer(this Microsoft.AspNetCore.Hosting.IWebHostBuilder! builder, System.Action<Microsoft.AspNetCore.TestHost.TestServerOptions!>! configureOptions) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder!
6365
static Microsoft.AspNetCore.TestHost.WebHostBuilderFactory.CreateFromAssemblyEntryPoint(System.Reflection.Assembly! assembly, string![]! args) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder?

src/Hosting/TestHost/src/WebHostBuilderExtensions.cs

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -115,20 +115,32 @@ public static IWebHostBuilder ConfigureTestContainer<TContainer>(this IWebHostBu
115115
return webHostBuilder;
116116
}
117117

118+
/// <summary>
119+
/// Sets the content root of relative to the <paramref name="solutionRelativePath" />.
120+
/// </summary>
121+
/// <param name="builder">The <see cref="IWebHostBuilder"/>.</param>
122+
/// <param name="solutionRelativePath">The directory of the solution file.</param>
123+
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
124+
public static IWebHostBuilder UseSolutionRelativeContentRoot(
125+
this IWebHostBuilder builder,
126+
string solutionRelativePath)
127+
{
128+
return builder.UseSolutionRelativeContentRoot(solutionRelativePath, AppContext.BaseDirectory, ["*.sln", "*.slnx"]);
129+
}
130+
118131
/// <summary>
119132
/// Sets the content root of relative to the <paramref name="solutionRelativePath" />.
120133
/// </summary>
121134
/// <param name="builder">The <see cref="IWebHostBuilder"/>.</param>
122135
/// <param name="solutionRelativePath">The directory of the solution file.</param>
123136
/// <param name="solutionName">The name of the solution file to make the content root relative to.</param>
124137
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
125-
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
126138
public static IWebHostBuilder UseSolutionRelativeContentRoot(
127139
this IWebHostBuilder builder,
128140
string solutionRelativePath,
129-
string solutionName = "*.sln")
141+
string solutionName)
130142
{
131-
return builder.UseSolutionRelativeContentRoot(solutionRelativePath, AppContext.BaseDirectory, solutionName);
143+
return builder.UseSolutionRelativeContentRoot(solutionRelativePath, AppContext.BaseDirectory, [solutionName]);
132144
}
133145

134146
/// <summary>
@@ -144,19 +156,40 @@ public static IWebHostBuilder UseSolutionRelativeContentRoot(
144156
this IWebHostBuilder builder,
145157
string solutionRelativePath,
146158
string applicationBasePath,
147-
string solutionName = "*.sln")
159+
string solutionName)
160+
{
161+
return builder.UseSolutionRelativeContentRoot(solutionRelativePath, applicationBasePath, [solutionName]);
162+
}
163+
164+
/// <summary>
165+
/// Sets the content root of relative to the <paramref name="solutionRelativePath" />.
166+
/// </summary>
167+
/// <param name="builder">The <see cref="IWebHostBuilder"/>.</param>
168+
/// <param name="solutionRelativePath">The directory of the solution file.</param>
169+
/// <param name="applicationBasePath">The root of the app's directory.</param>
170+
/// <param name="solutionNames">The names of the solution files to make the content root relative to.</param>
171+
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
172+
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
173+
public static IWebHostBuilder UseSolutionRelativeContentRoot(
174+
this IWebHostBuilder builder,
175+
string solutionRelativePath,
176+
string applicationBasePath,
177+
ReadOnlySpan<string> solutionNames)
148178
{
149179
ArgumentNullException.ThrowIfNull(solutionRelativePath);
150180
ArgumentNullException.ThrowIfNull(applicationBasePath);
151181

152182
var directoryInfo = new DirectoryInfo(applicationBasePath);
153183
do
154184
{
155-
var solutionPath = Directory.EnumerateFiles(directoryInfo.FullName, solutionName).FirstOrDefault();
156-
if (solutionPath != null)
185+
foreach (var solutionName in solutionNames)
157186
{
158-
builder.UseContentRoot(Path.GetFullPath(Path.Combine(directoryInfo.FullName, solutionRelativePath)));
159-
return builder;
187+
var solutionPath = Directory.EnumerateFiles(directoryInfo.FullName, solutionName).FirstOrDefault();
188+
if (solutionPath != null)
189+
{
190+
builder.UseContentRoot(Path.GetFullPath(Path.Combine(directoryInfo.FullName, solutionRelativePath)));
191+
return builder;
192+
}
160193
}
161194

162195
directoryInfo = directoryInfo.Parent;
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
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+
public class UseSolutionRelativeContentRootTests : IDisposable
10+
{
11+
private readonly string _tempDirectory;
12+
private readonly string _contentDirectory;
13+
14+
public UseSolutionRelativeContentRootTests()
15+
{
16+
_tempDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")[..8]);
17+
_contentDirectory = Path.Combine(_tempDirectory, "src");
18+
Directory.CreateDirectory(_contentDirectory);
19+
}
20+
21+
[Fact]
22+
public void UseSolutionRelativeContentRoot_FindsSolutionFile()
23+
{
24+
var solutionFile = Path.Combine(_tempDirectory, "TestApp.sln");
25+
File.WriteAllText(solutionFile, "Microsoft Visual Studio Solution File, Format Version 12.00");
26+
27+
var builder = new WebHostBuilder()
28+
.UseTestServer()
29+
.Configure(app => { });
30+
31+
builder.UseSolutionRelativeContentRoot("src", _tempDirectory, "TestApp.sln");
32+
33+
using var host = builder.Build();
34+
var environment = host.Services.GetRequiredService<IWebHostEnvironment>();
35+
36+
Assert.Equal(_contentDirectory, environment.ContentRootPath);
37+
Assert.True(Directory.Exists(environment.ContentRootPath));
38+
}
39+
40+
[Fact]
41+
public void UseSolutionRelativeContentRoot_FindsSlnxFile()
42+
{
43+
var solutionFile = Path.Combine(_tempDirectory, "TestApp.slnx");
44+
File.WriteAllText(solutionFile, """
45+
<Solution>
46+
<Configurations>
47+
<Configuration Name="Debug|Any CPU" />
48+
<Configuration Name="Release|Any CPU" />
49+
</Configurations>
50+
</Solution>
51+
""");
52+
53+
var builder = new WebHostBuilder()
54+
.UseTestServer()
55+
.Configure(app => { });
56+
57+
builder.UseSolutionRelativeContentRoot("src", _tempDirectory, "TestApp.slnx");
58+
59+
using var host = builder.Build();
60+
var environment = host.Services.GetRequiredService<IWebHostEnvironment>();
61+
62+
Assert.Equal(_contentDirectory, environment.ContentRootPath);
63+
Assert.True(Directory.Exists(environment.ContentRootPath));
64+
}
65+
66+
[Fact]
67+
public void UseSolutionRelativeContentRoot_DefaultOverload_FindsBothSolutionTypes()
68+
{
69+
var slnFile = Path.Combine(_tempDirectory, "TestApp.sln");
70+
var slnxFile = Path.Combine(_tempDirectory, "TestApp.slnx");
71+
File.WriteAllText(slnFile, "Microsoft Visual Studio Solution File, Format Version 12.00");
72+
File.WriteAllText(slnxFile, """
73+
<Solution>
74+
<Configurations>
75+
<Configuration Name="Debug|Any CPU" />
76+
</Configurations>
77+
</Solution>
78+
""");
79+
80+
var builder = new WebHostBuilder()
81+
.UseTestServer()
82+
.Configure(app => { });
83+
84+
builder.UseSolutionRelativeContentRoot("src", _tempDirectory, "TestApp.sln");
85+
86+
using var host = builder.Build();
87+
var environment = host.Services.GetRequiredService<IWebHostEnvironment>();
88+
89+
Assert.Equal(_contentDirectory, environment.ContentRootPath);
90+
Assert.True(Directory.Exists(environment.ContentRootPath));
91+
}
92+
93+
[Fact]
94+
public void UseSolutionRelativeContentRoot_WithMultipleSolutionNames_FindsFirst()
95+
{
96+
var slnxFile = Path.Combine(_tempDirectory, "TestApp.slnx");
97+
File.WriteAllText(slnxFile, """
98+
<Solution>
99+
<Configurations>
100+
<Configuration Name="Debug|Any CPU" />
101+
</Configurations>
102+
</Solution>
103+
""");
104+
105+
var builder = new WebHostBuilder()
106+
.UseTestServer()
107+
.Configure(app => { });
108+
109+
builder.UseSolutionRelativeContentRoot("src", _tempDirectory, ["TestApp.sln", "TestApp.slnx"]);
110+
111+
using var host = builder.Build();
112+
var environment = host.Services.GetRequiredService<IWebHostEnvironment>();
113+
114+
Assert.Equal(_contentDirectory, environment.ContentRootPath);
115+
Assert.True(Directory.Exists(environment.ContentRootPath));
116+
}
117+
118+
[Fact]
119+
public void UseSolutionRelativeContentRoot_ThrowsWhenSolutionNotFound()
120+
{
121+
var builder = new WebHostBuilder()
122+
.UseTestServer()
123+
.Configure(app => { });
124+
125+
var exception = Assert.Throws<InvalidOperationException>(() =>
126+
builder.UseSolutionRelativeContentRoot("src", _tempDirectory, "NonExistent.sln"));
127+
128+
Assert.Contains("Solution root could not be located", exception.Message);
129+
Assert.Contains(_tempDirectory, exception.Message);
130+
}
131+
132+
[Fact]
133+
public void UseSolutionRelativeContentRoot_SearchesParentDirectories()
134+
{
135+
var subDirectory = Path.Combine(_tempDirectory, "sub", "folder");
136+
Directory.CreateDirectory(subDirectory);
137+
138+
var solutionFile = Path.Combine(_tempDirectory, "TestApp.slnx");
139+
File.WriteAllText(solutionFile, """
140+
<Solution>
141+
<Configurations>
142+
<Configuration Name="Debug|Any CPU" />
143+
</Configurations>
144+
</Solution>
145+
""");
146+
147+
var builder = new WebHostBuilder()
148+
.UseTestServer()
149+
.Configure(app => { });
150+
151+
builder.UseSolutionRelativeContentRoot("src", subDirectory, "TestApp.slnx");
152+
153+
using var host = builder.Build();
154+
var environment = host.Services.GetRequiredService<IWebHostEnvironment>();
155+
156+
Assert.Equal(_contentDirectory, environment.ContentRootPath);
157+
Assert.True(Directory.Exists(environment.ContentRootPath));
158+
}
159+
160+
public void Dispose()
161+
{
162+
if (Directory.Exists(_tempDirectory))
163+
{
164+
Directory.Delete(_tempDirectory, recursive: true);
165+
}
166+
}
167+
}
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)