diff --git a/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj b/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj
index 142f928723f94b1fae5722f1e302cc1e9cf93160..5ac8032197c4684bb1273219a1ee04d0a0c7376c 100644
--- a/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj
+++ b/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj
@@ -14,7 +14,7 @@
     <Compile Include="$(SharedSourceRoot)RazorViews\*.cs" />
     <Compile Include="$(SharedSourceRoot)StackTrace\**\*.cs" />
     <Compile Include="$(SharedSourceRoot)ErrorPage\**\*.cs" />
-    <Compile Include="$(SharedSourceRoot)StaticWebAssets\**\*.cs" />
+    <Compile Include="$(SharedSourceRoot)StaticWebAssets\**\*.cs" LinkBase="StaticWebAssets" />
   </ItemGroup>
 
   <ItemGroup>
diff --git a/src/Hosting/Hosting/test/StaticWebAssets/ManifestStaticWebAssetsFileProviderTests.cs b/src/Hosting/Hosting/test/StaticWebAssets/ManifestStaticWebAssetsFileProviderTests.cs
index 24f537afc6e6bfd7f3c4a5fe004341e8b022b3ef..160aad77849c1fbe8a4700e53b7ce201a7a9506a 100644
--- a/src/Hosting/Hosting/test/StaticWebAssets/ManifestStaticWebAssetsFileProviderTests.cs
+++ b/src/Hosting/Hosting/test/StaticWebAssets/ManifestStaticWebAssetsFileProviderTests.cs
@@ -57,6 +57,229 @@ namespace Microsoft.AspNetCore.Hosting.Tests.StaticWebAssets
             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<IFileProvider>();
+            first.Setup(s => s.GetFileInfo("icon.png")).Returns(new TestFileInfo() { Name = "icon.png", Exists = true });
+            var second = new Mock<IFileProvider>();
+            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<IFileProvider>();
+            first.Setup(s => s.GetFileInfo("icon.png")).Returns(new TestFileInfo() { Name = "icon.png", Exists = true });
+            var second = new Mock<IFileProvider>();
+            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<IFileProvider>();
+            first.Setup(s => s.GetFileInfo("Subdir")).Returns(new TestFileInfo() { Name = "Subdir", Exists = true });
+            var second = new Mock<IFileProvider>();
+            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 61a5b330d4137a11c439367029e49e110824d665..17b50ab4937df0f7dbb9b396cfda9236855830d1 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 @@ namespace Microsoft.AspNetCore.StaticWebAssets
         {
             public override Dictionary<string, StaticWebAssetNode> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
             {
-                return new Dictionary<string, StaticWebAssetNode>(
-                    JsonSerializer.Deserialize<IDictionary<string, StaticWebAssetNode>>(ref reader, options)!,
-                    StaticWebAssetManifest.PathComparer);
+                var parsed = JsonSerializer.Deserialize<IDictionary<string, StaticWebAssetNode>>(ref reader, options)!;
+                var result = new Dictionary<string, StaticWebAssetNode>(StaticWebAssetManifest.PathComparer);
+                MergeChildren(parsed, result);
+                return result;
+
+                static void MergeChildren(
+                    IDictionary<string, StaticWebAssetNode> newChildren,
+                    IDictionary<string, StaticWebAssetNode> 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<string, StaticWebAssetNode> value, JsonSerializerOptions options)