Skip to content

Commit cfcc236

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

File tree

4 files changed

+322
-10
lines changed

4 files changed

+322
-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 = default(System.ReadOnlySpan<string!>)) -> 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: 48 additions & 8 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>
@@ -115,20 +117,32 @@ public static IWebHostBuilder ConfigureTestContainer<TContainer>(this IWebHostBu
115117
return webHostBuilder;
116118
}
117119

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

134148
/// <summary>
@@ -144,19 +158,45 @@ public static IWebHostBuilder UseSolutionRelativeContentRoot(
144158
this IWebHostBuilder builder,
145159
string solutionRelativePath,
146160
string applicationBasePath,
147-
string solutionName = "*.sln")
161+
string solutionName)
162+
{
163+
return builder.UseSolutionRelativeContentRoot(solutionRelativePath, applicationBasePath, [solutionName]);
164+
}
165+
166+
/// <summary>
167+
/// Sets the content root of relative to the <paramref name="solutionRelativePath" />.
168+
/// </summary>
169+
/// <param name="builder">The <see cref="IWebHostBuilder"/>.</param>
170+
/// <param name="solutionRelativePath">The directory of the solution file.</param>
171+
/// <param name="applicationBasePath">The root of the app's directory.</param>
172+
/// <param name="solutionNames">The names of the solution files to make the content root relative to. If empty, defaults to *.sln and *.slnx.</param>
173+
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
174+
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
175+
public static IWebHostBuilder UseSolutionRelativeContentRoot(
176+
this IWebHostBuilder builder,
177+
string solutionRelativePath,
178+
string applicationBasePath,
179+
ReadOnlySpan<string> solutionNames = default)
148180
{
149181
ArgumentNullException.ThrowIfNull(solutionRelativePath);
150182
ArgumentNullException.ThrowIfNull(applicationBasePath);
151183

184+
if (solutionNames.IsEmpty)
185+
{
186+
solutionNames = _defaultSolutionNames;
187+
}
188+
152189
var directoryInfo = new DirectoryInfo(applicationBasePath);
153190
do
154191
{
155-
var solutionPath = Directory.EnumerateFiles(directoryInfo.FullName, solutionName).FirstOrDefault();
156-
if (solutionPath != null)
192+
foreach (var solutionName in solutionNames)
157193
{
158-
builder.UseContentRoot(Path.GetFullPath(Path.Combine(directoryInfo.FullName, solutionRelativePath)));
159-
return builder;
194+
var solutionPath = Directory.EnumerateFiles(directoryInfo.FullName, solutionName).FirstOrDefault();
195+
if (solutionPath != null)
196+
{
197+
builder.UseContentRoot(Path.GetFullPath(Path.Combine(directoryInfo.FullName, solutionRelativePath)));
198+
return builder;
199+
}
160200
}
161201

162202
directoryInfo = directoryInfo.Parent;
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
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_FindsSlnFile()
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", applicationBasePath: _tempDirectory);
32+
33+
using var host = builder.Build();
34+
var environment = host.Services.GetRequiredService<IWebHostEnvironment>();
35+
36+
Assert.Equal(_contentDirectory, environment.ContentRootPath);
37+
}
38+
39+
[Fact]
40+
public void UseSolutionRelativeContentRoot_FindsSlnxFile()
41+
{
42+
var solutionFile = Path.Combine(_tempDirectory, "TestApp.slnx");
43+
File.WriteAllText(solutionFile, """
44+
<Solution>
45+
<Configurations>
46+
<Configuration Name="Debug|Any CPU" />
47+
<Configuration Name="Release|Any CPU" />
48+
</Configurations>
49+
</Solution>
50+
""");
51+
52+
var builder = new WebHostBuilder()
53+
.UseTestServer()
54+
.Configure(app => { });
55+
56+
builder.UseSolutionRelativeContentRoot("src", applicationBasePath: _tempDirectory);
57+
58+
using var host = builder.Build();
59+
var environment = host.Services.GetRequiredService<IWebHostEnvironment>();
60+
61+
Assert.Equal(_contentDirectory, environment.ContentRootPath);
62+
}
63+
64+
[Fact]
65+
public void UseSolutionRelativeContentRoot_WithSolutionName_FindsSpecifiedFile()
66+
{
67+
var subDirectory = Path.Combine(_tempDirectory, "sub");
68+
Directory.CreateDirectory(subDirectory);
69+
70+
var slnFile = Path.Combine(subDirectory, "TestApp.sln");
71+
var slnxFile = Path.Combine(_tempDirectory, "TestApp.slnx");
72+
File.WriteAllText(slnFile, "Microsoft Visual Studio Solution File, Format Version 12.00");
73+
File.WriteAllText(slnxFile, """
74+
<Solution>
75+
<Configurations>
76+
<Configuration Name="Debug|Any CPU" />
77+
</Configurations>
78+
</Solution>
79+
""");
80+
81+
var builder = new WebHostBuilder()
82+
.UseTestServer()
83+
.Configure(app => { });
84+
85+
builder.UseSolutionRelativeContentRoot("src", _tempDirectory, "*.slnx");
86+
87+
using var host = builder.Build();
88+
var environment = host.Services.GetRequiredService<IWebHostEnvironment>();
89+
90+
Assert.Equal(_contentDirectory, environment.ContentRootPath);
91+
}
92+
93+
[Fact]
94+
public void UseSolutionRelativeContentRoot_WithMultipleSolutionNames_FindsInCurrentDirectoryFirst()
95+
{
96+
var expectedPath = Path.Combine(_contentDirectory, "sub");
97+
Directory.CreateDirectory(expectedPath);
98+
99+
var slnFile = Path.Combine(_tempDirectory, "TestApp.sln");
100+
var slnxFile = Path.Combine(_contentDirectory, "TestApp.slnx");
101+
File.WriteAllText(slnFile, "Microsoft Visual Studio Solution File, Format Version 12.00");
102+
File.WriteAllText(slnxFile, """
103+
<Solution>
104+
<Configurations>
105+
<Configuration Name="Debug|Any CPU" />
106+
</Configurations>
107+
</Solution>
108+
""");
109+
110+
var builder = new WebHostBuilder()
111+
.UseTestServer()
112+
.Configure(app => { });
113+
114+
builder.UseSolutionRelativeContentRoot("sub", _contentDirectory, ["*.sln", "*.slnx"]);
115+
116+
using var host = builder.Build();
117+
var environment = host.Services.GetRequiredService<IWebHostEnvironment>();
118+
119+
Assert.Equal(expectedPath, environment.ContentRootPath);
120+
}
121+
122+
[Fact]
123+
public void UseSolutionRelativeContentRoot_WithMultipleSolutionNames_WorksWithMultipleFiles()
124+
{
125+
var slnFile = Path.Combine(_tempDirectory, "TestApp.sln");
126+
var slnxFile = Path.Combine(_tempDirectory, "TestApp.slnx");
127+
File.WriteAllText(slnFile, "Microsoft Visual Studio Solution File, Format Version 12.00");
128+
File.WriteAllText(slnxFile, """
129+
<Solution>
130+
<Configurations>
131+
<Configuration Name="Debug|Any CPU" />
132+
</Configurations>
133+
</Solution>
134+
""");
135+
136+
var builder = new WebHostBuilder()
137+
.UseTestServer()
138+
.Configure(app => { });
139+
140+
builder.UseSolutionRelativeContentRoot("src", applicationBasePath: _tempDirectory, solutionNames: ["*.sln", "*.slnx"]);
141+
142+
using var host = builder.Build();
143+
var environment = host.Services.GetRequiredService<IWebHostEnvironment>();
144+
145+
Assert.Equal(_contentDirectory, environment.ContentRootPath);
146+
}
147+
148+
[Fact]
149+
public void UseSolutionRelativeContentRoot_ThrowsWhenSolutionNotFound()
150+
{
151+
var builder = new WebHostBuilder()
152+
.UseTestServer()
153+
.Configure(app => { });
154+
155+
var exception = Assert.Throws<InvalidOperationException>(() =>
156+
builder.UseSolutionRelativeContentRoot("src", applicationBasePath: _tempDirectory));
157+
158+
Assert.Contains("Solution root could not be located", exception.Message);
159+
Assert.Contains(_tempDirectory, exception.Message);
160+
}
161+
162+
[Fact]
163+
public void UseSolutionRelativeContentRoot_WithSolutionName_SearchesParentDirectories()
164+
{
165+
var subDirectory = Path.Combine(_tempDirectory, "sub", "folder");
166+
Directory.CreateDirectory(subDirectory);
167+
168+
var solutionFile = Path.Combine(_tempDirectory, "TestApp.slnx");
169+
File.WriteAllText(solutionFile, """
170+
<Solution>
171+
<Configurations>
172+
<Configuration Name="Debug|Any CPU" />
173+
</Configurations>
174+
</Solution>
175+
""");
176+
177+
var builder = new WebHostBuilder()
178+
.UseTestServer()
179+
.Configure(app => { });
180+
181+
builder.UseSolutionRelativeContentRoot("src", subDirectory, "*.slnx");
182+
183+
using var host = builder.Build();
184+
var environment = host.Services.GetRequiredService<IWebHostEnvironment>();
185+
186+
Assert.Equal(_contentDirectory, environment.ContentRootPath);
187+
}
188+
189+
public void Dispose()
190+
{
191+
if (Directory.Exists(_tempDirectory))
192+
{
193+
Directory.Delete(_tempDirectory, recursive: true);
194+
}
195+
}
196+
}
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)