diff --git a/eng/Versions.props b/eng/Versions.props index 9fda53fc996b..a429a4f553c4 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -248,9 +248,9 @@ 0.192.0 3.0.0 7.2.2 - 4.0.0-beta4 - 91.0.4472.1900 - 4.0.0-beta4 + 4.0.0-rc1 + 93.0.4577.1500 + 4.0.0-rc1 1.4.0 4.0.0 2.2.4 diff --git a/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj b/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj index 142f928723f9..5ac8032197c4 100644 --- a/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj +++ b/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/Hosting/Hosting/test/StaticWebAssets/ManifestStaticWebAssetsFileProviderTests.cs b/src/Hosting/Hosting/test/StaticWebAssets/ManifestStaticWebAssetsFileProviderTests.cs index 24f537afc6e6..160aad77849c 100644 --- a/src/Hosting/Hosting/test/StaticWebAssets/ManifestStaticWebAssetsFileProviderTests.cs +++ b/src/Hosting/Hosting/test/StaticWebAssets/ManifestStaticWebAssetsFileProviderTests.cs @@ -57,6 +57,229 @@ public void GetFileInfoPrefixRespectsOsCaseSensitivity() Assert.Equal(expectedResult, file.Exists); } + [Theory] + [InlineData("/img/icon.png", true)] + [InlineData("/Img/hero.gif", true)] + // Note that we've changed the casing of the first segment + [InlineData("/Img/icon.png", false)] + [InlineData("/img/hero.gif", false)] + public void ParseWorksWithNodesThatOnlyDifferOnCasing(string path, bool exists) + { + exists = exists | OperatingSystem.IsWindows(); + // Arrange + using var memoryStream = new MemoryStream(); + using var writer = new StreamWriter(memoryStream); + writer.Write(@"{ + ""ContentRoots"": [ + ""D:\\path\\"", + ""D:\\other\\"" + ], + ""Root"": { + ""Children"": { + ""img"": { + ""Children"": { + ""icon.png"": { + ""Asset"": { + ""ContentRootIndex"": 0, + ""SubPath"": ""icon.png"" + } + } + } + }, + ""Img"": { + ""Children"": { + ""hero.gif"": { + ""Asset"": { + ""ContentRootIndex"": 1, + ""SubPath"": ""hero.gif"" + } + } + } + } + } + } +}"); + var first = new Mock(); + first.Setup(s => s.GetFileInfo("icon.png")).Returns(new TestFileInfo() { Name = "icon.png", Exists = true }); + var second = new Mock(); + second.Setup(s => s.GetFileInfo("hero.gif")).Returns(new TestFileInfo() { Name = "hero.gif", Exists = true }); + + writer.Flush(); + memoryStream.Seek(0, SeekOrigin.Begin); + var manifest = ManifestStaticWebAssetFileProvider.StaticWebAssetManifest.Parse(memoryStream); + var comparer = ManifestStaticWebAssetFileProvider.StaticWebAssetManifest.PathComparer; + + var provider = new ManifestStaticWebAssetFileProvider( + manifest, + contentRoot => contentRoot switch + { + "D:\\path\\" => first.Object, + "D:\\other\\" => second.Object, + _ => throw new InvalidOperationException("Unknown provider") + }); + + // Act + var file = provider.GetFileInfo(path); + + // Assert + Assert.Equal(exists, file.Exists); + } + + [Theory] + [InlineData("/img/Subdir/icon.png", true)] + [InlineData("/Img/subdir/hero.gif", true)] + // Note that we've changed the casing of the second segment + [InlineData("/img/subdir/icon.png", false)] + [InlineData("/Img/Subdir/hero.gif", false)] + public void ParseWorksWithMergesNodesRecursively(string path, bool exists) + { + // Arrange + exists = exists | OperatingSystem.IsWindows(); + var firstLevelCount = OperatingSystem.IsWindows() ? 1 : 2; + using var memoryStream = new MemoryStream(); + using var writer = new StreamWriter(memoryStream); + writer.Write(@"{ + ""ContentRoots"": [ + ""D:\\path\\"", + ""D:\\other\\"" + ], + ""Root"": { + ""Children"": { + ""img"": { + ""Children"": { + ""Subdir"": { + ""Children"": { + ""icon.png"": { + ""Asset"": { + ""ContentRootIndex"": 0, + ""SubPath"": ""icon.png"" + } + } + } + } + } + }, + ""Img"": { + ""Children"": { + ""subdir"": { + ""Children"": { + ""hero.gif"": { + ""Asset"": { + ""ContentRootIndex"": 1, + ""SubPath"": ""hero.gif"" + } + } + } + } + } + } + } + } +}"); + var first = new Mock(); + first.Setup(s => s.GetFileInfo("icon.png")).Returns(new TestFileInfo() { Name = "icon.png", Exists = true }); + var second = new Mock(); + second.Setup(s => s.GetFileInfo("hero.gif")).Returns(new TestFileInfo() { Name = "hero.gif", Exists = true }); + + writer.Flush(); + memoryStream.Seek(0, SeekOrigin.Begin); + var manifest = ManifestStaticWebAssetFileProvider.StaticWebAssetManifest.Parse(memoryStream); + var comparer = ManifestStaticWebAssetFileProvider.StaticWebAssetManifest.PathComparer; + + var provider = new ManifestStaticWebAssetFileProvider( + manifest, + contentRoot => contentRoot switch + { + "D:\\path\\" => first.Object, + "D:\\other\\" => second.Object, + _ => throw new InvalidOperationException("Unknown provider") + }); + + // Act + var file = provider.GetFileInfo(path); + + // Assert + Assert.Equal(exists, file.Exists); + Assert.Equal(firstLevelCount, manifest.Root.Children.Count); + Assert.All(manifest.Root.Children.Values, c => Assert.Single(c.Children)); + } + + [Theory] + [InlineData("/img/Subdir", true)] + [InlineData("/Img/subdir/hero.gif", true)] + // Note that we've changed the casing of the second segment + [InlineData("/img/subdir", false)] + [InlineData("/Img/Subdir/hero.gif", false)] + public void ParseWorksFolderAndFileWithDiferentCasing(string path, bool exists) + { + // Arrange + exists = exists | OperatingSystem.IsWindows(); + var firstLevelCount = OperatingSystem.IsWindows() ? 1 : 2; + using var memoryStream = new MemoryStream(); + using var writer = new StreamWriter(memoryStream); + // img/Subdir is a file without extension + writer.Write(@"{ + ""ContentRoots"": [ + ""D:\\path\\"", + ""D:\\other\\"" + ], + ""Root"": { + ""Children"": { + ""img"": { + ""Children"": { + ""Subdir"": { + ""Asset"": { + ""ContentRootIndex"": 0, + ""SubPath"": ""Subdir"" + } + } + } + }, + ""Img"": { + ""Children"": { + ""subdir"": { + ""Children"": { + ""hero.gif"": { + ""Asset"": { + ""ContentRootIndex"": 1, + ""SubPath"": ""hero.gif"" + } + } + } + } + } + } + } + } +}"); + var first = new Mock(); + first.Setup(s => s.GetFileInfo("Subdir")).Returns(new TestFileInfo() { Name = "Subdir", Exists = true }); + var second = new Mock(); + second.Setup(s => s.GetFileInfo("hero.gif")).Returns(new TestFileInfo() { Name = "hero.gif", Exists = true }); + + writer.Flush(); + memoryStream.Seek(0, SeekOrigin.Begin); + var manifest = ManifestStaticWebAssetFileProvider.StaticWebAssetManifest.Parse(memoryStream); + var comparer = ManifestStaticWebAssetFileProvider.StaticWebAssetManifest.PathComparer; + + var provider = new ManifestStaticWebAssetFileProvider( + manifest, + contentRoot => contentRoot switch + { + "D:\\path\\" => first.Object, + "D:\\other\\" => second.Object, + _ => throw new InvalidOperationException("Unknown provider") + }); + + // Act + var file = provider.GetFileInfo(path); + + // Assert + Assert.Equal(exists, file.Exists); + Assert.Equal(firstLevelCount, manifest.Root.Children.Count); + Assert.All(manifest.Root.Children.Values, c => Assert.Single(c.Children)); + } + [Fact] public void CanFindFileListedOnTheManifest() { diff --git a/src/Shared/StaticWebAssets/StaticWebAssetsFileProvider.cs b/src/Shared/StaticWebAssets/ManifestStaticWebAssetFileProvider.cs similarity index 85% rename from src/Shared/StaticWebAssets/StaticWebAssetsFileProvider.cs rename to src/Shared/StaticWebAssets/ManifestStaticWebAssetFileProvider.cs index 77b2f397982c..6221f2cc5968 100644 --- a/src/Shared/StaticWebAssets/StaticWebAssetsFileProvider.cs +++ b/src/Shared/StaticWebAssets/ManifestStaticWebAssetFileProvider.cs @@ -3,7 +3,6 @@ using System.Collections; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.Extensions.FileProviders; @@ -367,9 +366,58 @@ private sealed class OSBasedCaseConverter : JsonConverter Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return new Dictionary( - JsonSerializer.Deserialize>(ref reader, options)!, - StaticWebAssetManifest.PathComparer); + var parsed = JsonSerializer.Deserialize>(ref reader, options)!; + var result = new Dictionary(StaticWebAssetManifest.PathComparer); + MergeChildren(parsed, result); + return result; + + static void MergeChildren( + IDictionary newChildren, + IDictionary existing) + { + foreach (var (key, value) in newChildren) + { + if (!existing.TryGetValue(key, out var existingNode)) + { + existing.Add(key, value); + } + else + { + if (value.Patterns != null) + { + if (existingNode.Patterns == null) + { + existingNode.Patterns = value.Patterns; + } + else + { + if (value.Patterns.Length > 0) + { + var newList = new StaticWebAssetPattern[existingNode.Patterns.Length + value.Patterns.Length]; + existingNode.Patterns.CopyTo(newList, 0); + value.Patterns.CopyTo(newList, existingNode.Patterns.Length); + existingNode.Patterns = newList; + } + } + } + + if (value.Children != null) + { + if (existingNode.Children == null) + { + existingNode.Children = value.Children; + } + else + { + if (value.Children.Count > 0) + { + MergeChildren(value.Children, existingNode.Children); + } + } + } + } + } + } } public override void Write(Utf8JsonWriter writer, Dictionary value, JsonSerializerOptions options)