Skip to content

Commit 38dd49c

Browse files
authored
Use fullpath for WebRootPath in WebApplicationBuilder (#34140)
* Use fullpath for WebRootPath in WebApplicationBuilder * Return absolute contentRootPath if webRootPath is null * Use fallback ContentRootPath value in setter * Set file providers when paths are set * Add more tests and clean up property setters * Set initial directory back to CurrentDirectory * Resolve WebRootPath dynamically and set default * Fix up relative paths in tests * Update both file providers if ContentRootPath changes * Use GetCurrentDirectory() for test cases and update provider setter * Unsetting ContentRootPath falls back to CurrentDirectory * No-op if path values not changed in setter
1 parent a017f74 commit 38dd49c

File tree

4 files changed

+207
-42
lines changed

4 files changed

+207
-42
lines changed

src/DefaultBuilder/src/ConfigureWebHostBuilder.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ public IWebHostBuilder UseSetting(string key, string? value)
8888
else if (string.Equals(key, WebHostDefaults.ContentRootKey, StringComparison.OrdinalIgnoreCase))
8989
{
9090
_environment.ContentRootPath = value;
91-
_environment.ResolveFileProviders(_configuration);
9291
}
9392
else if (string.Equals(key, WebHostDefaults.EnvironmentKey, StringComparison.OrdinalIgnoreCase))
9493
{
@@ -97,7 +96,6 @@ public IWebHostBuilder UseSetting(string key, string? value)
9796
else if (string.Equals(key, WebHostDefaults.WebRootKey, StringComparison.OrdinalIgnoreCase))
9897
{
9998
_environment.WebRootPath = value;
100-
_environment.ResolveFileProviders(_configuration);
10199
}
102100

103101
return this;

src/DefaultBuilder/src/WebHostEnvironment.cs

Lines changed: 91 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using System.IO;
56
using System.Reflection;
67
using Microsoft.AspNetCore.Hosting;
@@ -15,44 +16,40 @@ internal class WebHostEnvironment : IWebHostEnvironment
1516
{
1617
private static readonly NullFileProvider NullFileProvider = new();
1718

18-
public WebHostEnvironment(Assembly? callingAssembly)
19+
private IFileProvider _contentRootFileProvider = NullFileProvider;
20+
private IFileProvider _webRootFileProvider = NullFileProvider;
21+
// ContentRootPath and WebRootPath are set to default! on
22+
// initialization to match the behavior in HostingEnvironment.
23+
private string _contentRootPath = default!;
24+
private string _webRootPath = default!;
25+
26+
public WebHostEnvironment(Assembly? callingAssembly = null)
1927
{
2028
ContentRootPath = Directory.GetCurrentDirectory();
2129

2230
ApplicationName = (callingAssembly ?? Assembly.GetEntryAssembly())?.GetName()?.Name ?? string.Empty;
2331
EnvironmentName = Environments.Production;
2432

25-
// This feels wrong, but HostingEnvironment also sets WebRoot to "default!".
26-
WebRootPath = default!;
27-
2833
// Default to /wwwroot if it exists.
2934
var wwwroot = Path.Combine(ContentRootPath, "wwwroot");
3035
if (Directory.Exists(wwwroot))
3136
{
3237
WebRootPath = wwwroot;
3338
}
3439

35-
ContentRootFileProvider = NullFileProvider;
36-
WebRootFileProvider = NullFileProvider;
37-
38-
ResolveFileProviders(new Configuration());
39-
}
40-
41-
// For testing
42-
internal WebHostEnvironment()
43-
{
44-
ApplicationName = default!;
45-
EnvironmentName = default!;
46-
ContentRootPath = default!;
47-
WebRootPath = default!;
48-
ContentRootFileProvider = default!;
49-
WebRootFileProvider = default!;
40+
if (this.IsDevelopment())
41+
{
42+
StaticWebAssetsLoader.UseStaticWebAssets(this, new Configuration());
43+
}
5044
}
5145

5246
public void ApplyConfigurationSettings(IConfiguration configuration)
5347
{
5448
ReadConfigurationSettings(configuration);
55-
ResolveFileProviders(configuration);
49+
if (this.IsDevelopment())
50+
{
51+
StaticWebAssetsLoader.UseStaticWebAssets(this, configuration);
52+
}
5653
}
5754

5855
internal void ReadConfigurationSettings(IConfiguration configuration)
@@ -80,12 +77,8 @@ internal void CopyPropertiesTo(IWebHostEnvironment destination)
8077
{
8178
destination.ApplicationName = ApplicationName;
8279
destination.EnvironmentName = EnvironmentName;
83-
8480
destination.ContentRootPath = ContentRootPath;
85-
destination.ContentRootFileProvider = ContentRootFileProvider;
86-
8781
destination.WebRootPath = WebRootPath;
88-
destination.WebRootFileProvider = WebRootFileProvider;
8982
}
9083

9184
public void ResolveFileProviders(IConfiguration configuration)
@@ -100,19 +93,85 @@ public void ResolveFileProviders(IConfiguration configuration)
10093
WebRootFileProvider = new PhysicalFileProvider(WebRootPath);
10194
}
10295

103-
if (this.IsDevelopment())
104-
{
105-
StaticWebAssetsLoader.UseStaticWebAssets(this, configuration);
106-
}
10796
}
10897

10998
public string ApplicationName { get; set; }
11099
public string EnvironmentName { get; set; }
111100

112-
public IFileProvider ContentRootFileProvider { get; set; }
113-
public string ContentRootPath { get; set; }
101+
public IFileProvider ContentRootFileProvider
102+
{
103+
get => _contentRootFileProvider;
104+
set => _contentRootFileProvider = value;
105+
}
114106

115-
public IFileProvider WebRootFileProvider { get; set; }
116-
public string WebRootPath { get; set; }
107+
public IFileProvider WebRootFileProvider
108+
{
109+
get => _webRootFileProvider;
110+
set => _webRootFileProvider = value;
111+
}
112+
113+
114+
public string ContentRootPath
115+
{
116+
get => _contentRootPath;
117+
set
118+
{
119+
// No-op if the value setting does not change
120+
var targetValue = string.IsNullOrEmpty(value)
121+
? Directory.GetCurrentDirectory()
122+
: ResolvePathToRoot(value, AppContext.BaseDirectory);
123+
if (targetValue == _contentRootPath)
124+
{
125+
return;
126+
}
127+
128+
_contentRootPath = targetValue;
129+
130+
/* Update both file providers if content root path changes */
131+
if (Directory.Exists(_contentRootPath))
132+
{
133+
_contentRootFileProvider = new PhysicalFileProvider(_contentRootPath);
134+
}
135+
if (Directory.Exists(WebRootPath))
136+
{
137+
_webRootFileProvider = new PhysicalFileProvider(WebRootPath);
138+
}
139+
}
140+
}
141+
142+
public string WebRootPath
143+
{
144+
get => ResolvePathToRoot(_webRootPath, ContentRootPath);
145+
set
146+
{
147+
// No-op if the value setting does not change
148+
var targetValue = string.IsNullOrEmpty(value) ? "wwwroot" : value;
149+
if (targetValue == _webRootPath)
150+
{
151+
return;
152+
}
153+
154+
_webRootPath = targetValue;
155+
if (Directory.Exists(WebRootPath))
156+
{
157+
_webRootFileProvider = new PhysicalFileProvider(WebRootPath);
158+
}
159+
}
160+
}
161+
162+
private string ResolvePathToRoot(string relativePath, string basePath)
163+
{
164+
if (string.IsNullOrEmpty(relativePath))
165+
{
166+
return Path.GetFullPath(basePath);
167+
}
168+
169+
if (Path.IsPathRooted(relativePath))
170+
{
171+
return relativePath;
172+
}
173+
174+
return Path.Combine(Path.GetFullPath(basePath), relativePath);
175+
}
117176
}
118177
}

src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,30 @@ public void WebApplicationBuilder_CanClearDefaultLoggers()
304304
args.Payload.OfType<string>().Any(p => p.Contains(guid)));
305305
}
306306

307+
[Theory]
308+
[InlineData(true)]
309+
[InlineData(false)]
310+
public void WebApplicationBuilder_CanSetWebRootPaths(bool useSetter)
311+
{
312+
var builder = WebApplication.CreateBuilder();
313+
var webRootPath = "www";
314+
var fullWebRootPath = Path.Combine(Directory.GetCurrentDirectory(), webRootPath);
315+
316+
if (useSetter)
317+
{
318+
builder.Environment.WebRootPath = webRootPath;
319+
}
320+
else
321+
{
322+
builder.WebHost.UseWebRoot(webRootPath);
323+
Assert.Equal(webRootPath, builder.WebHost.GetSetting("webroot"));
324+
}
325+
326+
327+
var app = builder.Build();
328+
Assert.Equal(fullWebRootPath, app.Environment.WebRootPath);
329+
}
330+
307331
private class TestEventListener : EventListener
308332
{
309333
private volatile bool _disposed;

src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebHostEnvironmentTests.cs

Lines changed: 92 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.IO;
56
using System.Collections.Generic;
67
using Microsoft.AspNetCore.Builder;
78
using Microsoft.AspNetCore.Hosting;
@@ -34,8 +35,9 @@ public void ApplyConfigurationSettingsUsesTheCorrectKeys()
3435

3536
Assert.Equal(WebHostDefaults.ApplicationKey, env.ApplicationName);
3637
Assert.Equal(WebHostDefaults.EnvironmentKey, env.EnvironmentName);
37-
Assert.Equal(WebHostDefaults.ContentRootKey, env.ContentRootPath);
38-
Assert.Equal(WebHostDefaults.WebRootKey, env.WebRootPath);
38+
Assert.Equal(Path.Combine(Directory.GetCurrentDirectory(), WebHostDefaults.ContentRootKey), env.ContentRootPath);
39+
var fullWebRootPath = Path.Combine(env.ContentRootPath, env.WebRootPath);
40+
Assert.Equal(fullWebRootPath, env.WebRootPath);
3941
}
4042

4143
[Fact]
@@ -58,16 +60,95 @@ public void ApplyEnvironmentSettingsUsesTheCorrectKeysAndProperties()
5860

5961
Assert.Equal(WebHostDefaults.ApplicationKey, settings[WebHostDefaults.ApplicationKey]);
6062
Assert.Equal(WebHostDefaults.EnvironmentKey, settings[WebHostDefaults.EnvironmentKey]);
61-
Assert.Equal(WebHostDefaults.ContentRootKey, settings[WebHostDefaults.ContentRootKey]);
62-
Assert.Equal(WebHostDefaults.WebRootKey, settings[WebHostDefaults.WebRootKey]);
63+
Assert.Equal(Path.Combine(Directory.GetCurrentDirectory(), WebHostDefaults.ContentRootKey), settings[WebHostDefaults.ContentRootKey]);
64+
var fullWebRootPath = Path.Combine(settings[WebHostDefaults.ContentRootKey], settings[WebHostDefaults.WebRootKey]);
65+
Assert.Equal(fullWebRootPath, settings[WebHostDefaults.WebRootKey]);
6366

6467
Assert.Equal(WebHostDefaults.ApplicationKey, webHostBuilderEnvironment.ApplicationName);
6568
Assert.Equal(WebHostDefaults.EnvironmentKey, webHostBuilderEnvironment.EnvironmentName);
66-
Assert.Equal(WebHostDefaults.ContentRootKey, webHostBuilderEnvironment.ContentRootPath);
67-
Assert.Equal(WebHostDefaults.WebRootKey, webHostBuilderEnvironment.WebRootPath);
69+
Assert.Equal(Path.Combine(Directory.GetCurrentDirectory(), WebHostDefaults.ContentRootKey), webHostBuilderEnvironment.ContentRootPath);
70+
Assert.Equal(fullWebRootPath, webHostBuilderEnvironment.WebRootPath);
6871

69-
Assert.Same(originalEnvironment.ContentRootFileProvider, webHostBuilderEnvironment.ContentRootFileProvider);
70-
Assert.Same(originalEnvironment.WebRootFileProvider, webHostBuilderEnvironment.WebRootFileProvider);
72+
Assert.NotEqual(originalEnvironment.ContentRootFileProvider, webHostBuilderEnvironment.ContentRootFileProvider);
73+
Assert.NotEqual(originalEnvironment.WebRootFileProvider, webHostBuilderEnvironment.WebRootFileProvider);
74+
}
75+
76+
[Fact]
77+
public void SettingPathsSetsContentProviders()
78+
{
79+
var environment = new WebHostEnvironment();
80+
var tempPath = Path.GetTempPath();
81+
82+
environment.ContentRootPath = tempPath;
83+
environment.WebRootPath = tempPath;
84+
85+
Assert.Equal(tempPath, environment.WebRootPath);
86+
Assert.Equal(tempPath, environment.ContentRootPath);
87+
88+
Assert.IsType<PhysicalFileProvider>(environment.ContentRootFileProvider);
89+
Assert.IsType<PhysicalFileProvider>(environment.WebRootFileProvider);
90+
91+
Assert.Equal(EnsureTrailingSlash(tempPath), ((PhysicalFileProvider)environment.ContentRootFileProvider).Root);
92+
Assert.Equal(EnsureTrailingSlash(tempPath), ((PhysicalFileProvider)environment.WebRootFileProvider).Root);
93+
}
94+
95+
[Fact]
96+
public void RelativePathsAreMappedToFullPaths()
97+
{
98+
var environment = new WebHostEnvironment();
99+
var relativeRootPath = "some-relative-path";
100+
var relativeSubPath = "some-other-relative-path";
101+
var fullContentRoot = Path.Combine(AppContext.BaseDirectory, relativeRootPath);
102+
103+
// ContentRootPath is mapped relative to AppContext.BaseDirectory
104+
environment.ContentRootPath = relativeRootPath;
105+
Assert.Equal(fullContentRoot, environment.ContentRootPath);
106+
107+
// WebRootPath is mapped relative to ContentRootPath
108+
environment.WebRootPath = relativeSubPath;
109+
Assert.Equal(Path.Combine(fullContentRoot, relativeSubPath), environment.WebRootPath);
110+
}
111+
112+
[Fact]
113+
public void UnsettingPathsFallsBackToDefaults()
114+
{
115+
var environment = new WebHostEnvironment();
116+
var defaultWebRootPath = Path.Combine(environment.ContentRootPath, "wwwroot");
117+
var webRootPath = Path.GetTempPath();
118+
119+
environment.WebRootPath = webRootPath;
120+
121+
Assert.Equal(webRootPath, environment.WebRootPath);
122+
Assert.Equal(EnsureTrailingSlash(webRootPath), ((PhysicalFileProvider)environment.WebRootFileProvider).Root);
123+
124+
// Setting WebRootPath to fallsback to default
125+
environment.WebRootPath = null;
126+
Assert.Equal(defaultWebRootPath, environment.WebRootPath);
127+
128+
// Setting ContentRootPath to null falls back to CurrentDirectory
129+
environment.ContentRootPath = null;
130+
Assert.Equal(Directory.GetCurrentDirectory(), environment.ContentRootPath);
131+
Assert.Equal(EnsureTrailingSlash(Directory.GetCurrentDirectory()), ((PhysicalFileProvider)environment.ContentRootFileProvider).Root);
132+
}
133+
134+
[Fact]
135+
public void SetContentRootAfterRelativeWebRoot()
136+
{
137+
var environment = new WebHostEnvironment();
138+
var webRootPath = "some-relative-path";
139+
var tempPath = Path.GetTempPath();
140+
141+
environment.WebRootPath = webRootPath;
142+
143+
Assert.Equal(Path.Combine(Directory.GetCurrentDirectory(), webRootPath), environment.WebRootPath);
144+
Assert.Equal(Directory.GetCurrentDirectory(), environment.ContentRootPath);
145+
146+
// Setting the ContentRootPath after setting a relative WebRootPath
147+
environment.ContentRootPath = tempPath;
148+
149+
Assert.Equal(tempPath, environment.ContentRootPath);
150+
Assert.Equal(EnsureTrailingSlash(tempPath), ((PhysicalFileProvider)environment.ContentRootFileProvider).Root);
151+
Assert.Equal(Path.Combine(tempPath, webRootPath), environment.WebRootPath);
71152
}
72153

73154
private class TestWebHostBuilder : IWebHostBuilder
@@ -122,5 +203,8 @@ public IWebHostBuilder UseSetting(string key, string value)
122203
return this;
123204
}
124205
}
206+
207+
private static string EnsureTrailingSlash(string path)
208+
=> path.EndsWith(Path.DirectorySeparatorChar) ? path : path + Path.DirectorySeparatorChar;
125209
}
126210
}

0 commit comments

Comments
 (0)