Skip to content

Commit

Permalink
Use fullpath for WebRootPath in WebApplicationBuilder (dotnet#34140)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
captainsafia authored Jul 13, 2021
1 parent a017f74 commit 38dd49c
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 42 deletions.
2 changes: 0 additions & 2 deletions src/DefaultBuilder/src/ConfigureWebHostBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ public IWebHostBuilder UseSetting(string key, string? value)
else if (string.Equals(key, WebHostDefaults.ContentRootKey, StringComparison.OrdinalIgnoreCase))
{
_environment.ContentRootPath = value;
_environment.ResolveFileProviders(_configuration);
}
else if (string.Equals(key, WebHostDefaults.EnvironmentKey, StringComparison.OrdinalIgnoreCase))
{
Expand All @@ -97,7 +96,6 @@ public IWebHostBuilder UseSetting(string key, string? value)
else if (string.Equals(key, WebHostDefaults.WebRootKey, StringComparison.OrdinalIgnoreCase))
{
_environment.WebRootPath = value;
_environment.ResolveFileProviders(_configuration);
}

return this;
Expand Down
123 changes: 91 additions & 32 deletions src/DefaultBuilder/src/WebHostEnvironment.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.IO;
using System.Reflection;
using Microsoft.AspNetCore.Hosting;
Expand All @@ -15,44 +16,40 @@ internal class WebHostEnvironment : IWebHostEnvironment
{
private static readonly NullFileProvider NullFileProvider = new();

public WebHostEnvironment(Assembly? callingAssembly)
private IFileProvider _contentRootFileProvider = NullFileProvider;
private IFileProvider _webRootFileProvider = NullFileProvider;
// ContentRootPath and WebRootPath are set to default! on
// initialization to match the behavior in HostingEnvironment.
private string _contentRootPath = default!;
private string _webRootPath = default!;

public WebHostEnvironment(Assembly? callingAssembly = null)
{
ContentRootPath = Directory.GetCurrentDirectory();

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

// This feels wrong, but HostingEnvironment also sets WebRoot to "default!".
WebRootPath = default!;

// Default to /wwwroot if it exists.
var wwwroot = Path.Combine(ContentRootPath, "wwwroot");
if (Directory.Exists(wwwroot))
{
WebRootPath = wwwroot;
}

ContentRootFileProvider = NullFileProvider;
WebRootFileProvider = NullFileProvider;

ResolveFileProviders(new Configuration());
}

// For testing
internal WebHostEnvironment()
{
ApplicationName = default!;
EnvironmentName = default!;
ContentRootPath = default!;
WebRootPath = default!;
ContentRootFileProvider = default!;
WebRootFileProvider = default!;
if (this.IsDevelopment())
{
StaticWebAssetsLoader.UseStaticWebAssets(this, new Configuration());
}
}

public void ApplyConfigurationSettings(IConfiguration configuration)
{
ReadConfigurationSettings(configuration);
ResolveFileProviders(configuration);
if (this.IsDevelopment())
{
StaticWebAssetsLoader.UseStaticWebAssets(this, configuration);
}
}

internal void ReadConfigurationSettings(IConfiguration configuration)
Expand Down Expand Up @@ -80,12 +77,8 @@ internal void CopyPropertiesTo(IWebHostEnvironment destination)
{
destination.ApplicationName = ApplicationName;
destination.EnvironmentName = EnvironmentName;

destination.ContentRootPath = ContentRootPath;
destination.ContentRootFileProvider = ContentRootFileProvider;

destination.WebRootPath = WebRootPath;
destination.WebRootFileProvider = WebRootFileProvider;
}

public void ResolveFileProviders(IConfiguration configuration)
Expand All @@ -100,19 +93,85 @@ public void ResolveFileProviders(IConfiguration configuration)
WebRootFileProvider = new PhysicalFileProvider(WebRootPath);
}

if (this.IsDevelopment())
{
StaticWebAssetsLoader.UseStaticWebAssets(this, configuration);
}
}

public string ApplicationName { get; set; }
public string EnvironmentName { get; set; }

public IFileProvider ContentRootFileProvider { get; set; }
public string ContentRootPath { get; set; }
public IFileProvider ContentRootFileProvider
{
get => _contentRootFileProvider;
set => _contentRootFileProvider = value;
}

public IFileProvider WebRootFileProvider { get; set; }
public string WebRootPath { get; set; }
public IFileProvider WebRootFileProvider
{
get => _webRootFileProvider;
set => _webRootFileProvider = value;
}


public string ContentRootPath
{
get => _contentRootPath;
set
{
// No-op if the value setting does not change
var targetValue = string.IsNullOrEmpty(value)
? Directory.GetCurrentDirectory()
: ResolvePathToRoot(value, AppContext.BaseDirectory);
if (targetValue == _contentRootPath)
{
return;
}

_contentRootPath = targetValue;

/* Update both file providers if content root path changes */
if (Directory.Exists(_contentRootPath))
{
_contentRootFileProvider = new PhysicalFileProvider(_contentRootPath);
}
if (Directory.Exists(WebRootPath))
{
_webRootFileProvider = new PhysicalFileProvider(WebRootPath);
}
}
}

public string WebRootPath
{
get => ResolvePathToRoot(_webRootPath, ContentRootPath);
set
{
// No-op if the value setting does not change
var targetValue = string.IsNullOrEmpty(value) ? "wwwroot" : value;
if (targetValue == _webRootPath)
{
return;
}

_webRootPath = targetValue;
if (Directory.Exists(WebRootPath))
{
_webRootFileProvider = new PhysicalFileProvider(WebRootPath);
}
}
}

private string ResolvePathToRoot(string relativePath, string basePath)
{
if (string.IsNullOrEmpty(relativePath))
{
return Path.GetFullPath(basePath);
}

if (Path.IsPathRooted(relativePath))
{
return relativePath;
}

return Path.Combine(Path.GetFullPath(basePath), relativePath);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,30 @@ public void WebApplicationBuilder_CanClearDefaultLoggers()
args.Payload.OfType<string>().Any(p => p.Contains(guid)));
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void WebApplicationBuilder_CanSetWebRootPaths(bool useSetter)
{
var builder = WebApplication.CreateBuilder();
var webRootPath = "www";
var fullWebRootPath = Path.Combine(Directory.GetCurrentDirectory(), webRootPath);

if (useSetter)
{
builder.Environment.WebRootPath = webRootPath;
}
else
{
builder.WebHost.UseWebRoot(webRootPath);
Assert.Equal(webRootPath, builder.WebHost.GetSetting("webroot"));
}


var app = builder.Build();
Assert.Equal(fullWebRootPath, app.Environment.WebRootPath);
}

private class TestEventListener : EventListener
{
private volatile bool _disposed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
Expand Down Expand Up @@ -34,8 +35,9 @@ public void ApplyConfigurationSettingsUsesTheCorrectKeys()

Assert.Equal(WebHostDefaults.ApplicationKey, env.ApplicationName);
Assert.Equal(WebHostDefaults.EnvironmentKey, env.EnvironmentName);
Assert.Equal(WebHostDefaults.ContentRootKey, env.ContentRootPath);
Assert.Equal(WebHostDefaults.WebRootKey, env.WebRootPath);
Assert.Equal(Path.Combine(Directory.GetCurrentDirectory(), WebHostDefaults.ContentRootKey), env.ContentRootPath);
var fullWebRootPath = Path.Combine(env.ContentRootPath, env.WebRootPath);
Assert.Equal(fullWebRootPath, env.WebRootPath);
}

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

Assert.Equal(WebHostDefaults.ApplicationKey, settings[WebHostDefaults.ApplicationKey]);
Assert.Equal(WebHostDefaults.EnvironmentKey, settings[WebHostDefaults.EnvironmentKey]);
Assert.Equal(WebHostDefaults.ContentRootKey, settings[WebHostDefaults.ContentRootKey]);
Assert.Equal(WebHostDefaults.WebRootKey, settings[WebHostDefaults.WebRootKey]);
Assert.Equal(Path.Combine(Directory.GetCurrentDirectory(), WebHostDefaults.ContentRootKey), settings[WebHostDefaults.ContentRootKey]);
var fullWebRootPath = Path.Combine(settings[WebHostDefaults.ContentRootKey], settings[WebHostDefaults.WebRootKey]);
Assert.Equal(fullWebRootPath, settings[WebHostDefaults.WebRootKey]);

Assert.Equal(WebHostDefaults.ApplicationKey, webHostBuilderEnvironment.ApplicationName);
Assert.Equal(WebHostDefaults.EnvironmentKey, webHostBuilderEnvironment.EnvironmentName);
Assert.Equal(WebHostDefaults.ContentRootKey, webHostBuilderEnvironment.ContentRootPath);
Assert.Equal(WebHostDefaults.WebRootKey, webHostBuilderEnvironment.WebRootPath);
Assert.Equal(Path.Combine(Directory.GetCurrentDirectory(), WebHostDefaults.ContentRootKey), webHostBuilderEnvironment.ContentRootPath);
Assert.Equal(fullWebRootPath, webHostBuilderEnvironment.WebRootPath);

Assert.Same(originalEnvironment.ContentRootFileProvider, webHostBuilderEnvironment.ContentRootFileProvider);
Assert.Same(originalEnvironment.WebRootFileProvider, webHostBuilderEnvironment.WebRootFileProvider);
Assert.NotEqual(originalEnvironment.ContentRootFileProvider, webHostBuilderEnvironment.ContentRootFileProvider);
Assert.NotEqual(originalEnvironment.WebRootFileProvider, webHostBuilderEnvironment.WebRootFileProvider);
}

[Fact]
public void SettingPathsSetsContentProviders()
{
var environment = new WebHostEnvironment();
var tempPath = Path.GetTempPath();

environment.ContentRootPath = tempPath;
environment.WebRootPath = tempPath;

Assert.Equal(tempPath, environment.WebRootPath);
Assert.Equal(tempPath, environment.ContentRootPath);

Assert.IsType<PhysicalFileProvider>(environment.ContentRootFileProvider);
Assert.IsType<PhysicalFileProvider>(environment.WebRootFileProvider);

Assert.Equal(EnsureTrailingSlash(tempPath), ((PhysicalFileProvider)environment.ContentRootFileProvider).Root);
Assert.Equal(EnsureTrailingSlash(tempPath), ((PhysicalFileProvider)environment.WebRootFileProvider).Root);
}

[Fact]
public void RelativePathsAreMappedToFullPaths()
{
var environment = new WebHostEnvironment();
var relativeRootPath = "some-relative-path";
var relativeSubPath = "some-other-relative-path";
var fullContentRoot = Path.Combine(AppContext.BaseDirectory, relativeRootPath);

// ContentRootPath is mapped relative to AppContext.BaseDirectory
environment.ContentRootPath = relativeRootPath;
Assert.Equal(fullContentRoot, environment.ContentRootPath);

// WebRootPath is mapped relative to ContentRootPath
environment.WebRootPath = relativeSubPath;
Assert.Equal(Path.Combine(fullContentRoot, relativeSubPath), environment.WebRootPath);
}

[Fact]
public void UnsettingPathsFallsBackToDefaults()
{
var environment = new WebHostEnvironment();
var defaultWebRootPath = Path.Combine(environment.ContentRootPath, "wwwroot");
var webRootPath = Path.GetTempPath();

environment.WebRootPath = webRootPath;

Assert.Equal(webRootPath, environment.WebRootPath);
Assert.Equal(EnsureTrailingSlash(webRootPath), ((PhysicalFileProvider)environment.WebRootFileProvider).Root);

// Setting WebRootPath to fallsback to default
environment.WebRootPath = null;
Assert.Equal(defaultWebRootPath, environment.WebRootPath);

// Setting ContentRootPath to null falls back to CurrentDirectory
environment.ContentRootPath = null;
Assert.Equal(Directory.GetCurrentDirectory(), environment.ContentRootPath);
Assert.Equal(EnsureTrailingSlash(Directory.GetCurrentDirectory()), ((PhysicalFileProvider)environment.ContentRootFileProvider).Root);
}

[Fact]
public void SetContentRootAfterRelativeWebRoot()
{
var environment = new WebHostEnvironment();
var webRootPath = "some-relative-path";
var tempPath = Path.GetTempPath();

environment.WebRootPath = webRootPath;

Assert.Equal(Path.Combine(Directory.GetCurrentDirectory(), webRootPath), environment.WebRootPath);
Assert.Equal(Directory.GetCurrentDirectory(), environment.ContentRootPath);

// Setting the ContentRootPath after setting a relative WebRootPath
environment.ContentRootPath = tempPath;

Assert.Equal(tempPath, environment.ContentRootPath);
Assert.Equal(EnsureTrailingSlash(tempPath), ((PhysicalFileProvider)environment.ContentRootFileProvider).Root);
Assert.Equal(Path.Combine(tempPath, webRootPath), environment.WebRootPath);
}

private class TestWebHostBuilder : IWebHostBuilder
Expand Down Expand Up @@ -122,5 +203,8 @@ public IWebHostBuilder UseSetting(string key, string value)
return this;
}
}

private static string EnsureTrailingSlash(string path)
=> path.EndsWith(Path.DirectorySeparatorChar) ? path : path + Path.DirectorySeparatorChar;
}
}

0 comments on commit 38dd49c

Please sign in to comment.