diff --git a/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.Compression.targets b/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.Compression.targets
index 1042c438908f..4bd2e53ba420 100644
--- a/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.Compression.targets
+++ b/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.Compression.targets
@@ -161,11 +161,12 @@ Copyright (c) .NET Foundation. All rights reserved.
- $(BuildCompressionFormats);gzip
- $(PublishCompressionFormats);gzip;brotli
+ true
+ $(BuildCompressionFormats);gzip
+ $(PublishCompressionFormats);gzip;brotli
false
- $(CompressionIncludePatterns)
- $(CompressionExcludePatterns)
+
+ $(_BlazorBrotliCompressionLevel)
@@ -195,7 +196,7 @@ Copyright (c) .NET Foundation. All rights reserved.
GeneratePublishCompressedStaticWebAssets
ResolvePublishCompressedStaticWebAssetsConfiguration
-->
-
+
ResolvePublishCompressedStaticWebAssets;
$(ResolvePublishRelatedStaticWebAssetsDependsOn)
@@ -216,7 +217,7 @@ Copyright (c) .NET Foundation. All rights reserved.
<_DotNetHostFileName Condition="'$(OS)' == 'Windows_NT'">dotnet.exe
-
+
diff --git a/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.targets b/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.targets
index c922d5b571c8..fd3f1f68c2bd 100644
--- a/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.targets
+++ b/src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.targets
@@ -660,7 +660,7 @@ Copyright (c) .NET Foundation. All rights reserved.
DependsOnTargets="ResolveStaticWebAssetsConfiguration;UpdateExistingPackageStaticWebAssets">
a.Identity);
+ var assetsById = new Dictionary(CandidateAssets.Length, OSPath.PathComparer);
- var endpointsByAsset = CandidateEndpoints.Select(StaticWebAssetEndpoint.FromTaskItem)
- .GroupBy(e => e.AssetFile)
- .ToDictionary(g => g.Key, g => g.ToList());
+ // A good rule of thumb is that the number of compressed assets is half the number of assets.
+ var compressedAssets = new List(CandidateAssets.Length / 2);
- var compressedAssets = assetsById.Values.Where(a => a.AssetTraitName == "Content-Encoding").ToList();
- var updatedEndpoints = new HashSet(StaticWebAssetEndpoint.RouteAndAssetComparer);
+ for (var i = 0; i < CandidateAssets.Length; i++)
+ {
+ var candidate = StaticWebAsset.FromTaskItem(CandidateAssets[i]);
+ if (assetsById.ContainsKey(CandidateAssets[i].ItemSpec))
+ {
+ Log.LogWarning("Detected duplicated asset '{0}'. Skipping the asset because it was already processed.", candidate.Identity);
+ continue;
+ }
+
+ assetsById[candidate.Identity] = candidate;
+ if (string.Equals(candidate.AssetTraitName, "Content-Encoding", StringComparison.Ordinal))
+ {
+ compressedAssets.Add(candidate);
+ }
+ }
+ var endpointsByAsset = new Dictionary>(CandidateEndpoints.Length, StringComparer.OrdinalIgnoreCase);
+ for (var i = 0; i < CandidateEndpoints.Length; i++)
+ {
+ var endpoint = StaticWebAssetEndpoint.FromTaskItem(CandidateEndpoints[i]);
+ if (!endpointsByAsset.TryGetValue(endpoint.AssetFile, out var endpoints))
+ {
+ endpoints = [];
+ endpointsByAsset[endpoint.AssetFile] = endpoints;
+ }
+ endpoints.Add(endpoint);
+ }
+
+ var updatedEndpoints = new HashSet(StaticWebAssetEndpoint.RouteAndAssetComparer);
var preservedEndpoints = new Dictionary<(string, string), StaticWebAssetEndpoint>();
// Add response headers to compressed endpoints
@@ -39,20 +64,20 @@ public override bool Execute()
{
if (!assetsById.TryGetValue(compressedAsset.RelatedAsset, out var relatedAsset))
{
- Log.LogWarning("Related asset not found for compressed asset: {0}", compressedAsset.Identity);
- throw new InvalidOperationException($"Related asset not found for compressed asset: {compressedAsset.Identity}");
+ Log.LogWarning("Related asset '{0}' not found for compressed asset: '{1}'. Skipping asset", compressedAsset.RelatedAsset, compressedAsset.Identity);
+ continue;
}
if (!endpointsByAsset.TryGetValue(compressedAsset.Identity, out var compressedEndpoints))
{
- Log.LogWarning("Endpoints not found for compressed asset: {0} {1}", compressedAsset.RelativePath, compressedAsset.Identity);
- throw new InvalidOperationException($"Endpoints not found for compressed asset: {compressedAsset.Identity}");
+ Log.LogWarning("Endpoints not found for compressed asset: '{0}' '{1}'. Skipping asset", compressedAsset.RelativePath, compressedAsset.Identity);
+ continue;
}
if (!endpointsByAsset.TryGetValue(relatedAsset.Identity, out var relatedAssetEndpoints))
{
- Log.LogWarning("Endpoints not found for related asset: {0}", relatedAsset.Identity);
- throw new InvalidOperationException($"Endpoints not found for related asset: {relatedAsset.Identity}");
+ Log.LogWarning("Endpoints not found for related asset: '{0}'. Skipping asset", relatedAsset.Identity);
+ continue;
}
Log.LogMessage("Processing compressed asset: {0}", compressedAsset.Identity);
diff --git a/src/StaticWebAssetsSdk/Tasks/Compression/DiscoverPrecompressedAssets.cs b/src/StaticWebAssetsSdk/Tasks/Compression/DiscoverPrecompressedAssets.cs
index a4a7357fa0c6..e78120ac75c0 100644
--- a/src/StaticWebAssetsSdk/Tasks/Compression/DiscoverPrecompressedAssets.cs
+++ b/src/StaticWebAssetsSdk/Tasks/Compression/DiscoverPrecompressedAssets.cs
@@ -29,7 +29,19 @@ public override bool Execute()
var candidates = CandidateAssets.Select(StaticWebAsset.FromTaskItem).ToArray();
var assetsToUpdate = new List();
- var candidatesByIdentity = candidates.ToDictionary(asset => asset.Identity, OSPath.PathComparer);
+ var candidatesByIdentity = new Dictionary(CandidateAssets.Length, OSPath.PathComparer);
+ for (var i = 0; i < candidates.Length; i++)
+ {
+ var candidate = candidates[i];
+ if (candidatesByIdentity.ContainsKey(candidate.Identity))
+ {
+ Log.LogMessage(MessageImportance.Low,
+ "Detected duplicated asset '{0}'. Skipping the asset because it was already processed.",
+ candidate.Identity);
+ return false;
+ }
+ candidatesByIdentity[candidate.Identity] = candidate;
+ }
foreach (var candidate in candidates)
{
diff --git a/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssets/ApplyCompressionNegotiationTest.cs b/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssets/ApplyCompressionNegotiationTest.cs
index 48524f5b6b6a..168a6cd0be20 100644
--- a/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssets/ApplyCompressionNegotiationTest.cs
+++ b/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssets/ApplyCompressionNegotiationTest.cs
@@ -1128,6 +1128,60 @@ string candidate when candidate.EndsWith(Path.Combine("compressed", "candidate.j
]);
}
+ [Fact]
+ public void AppliesContentNegotiationRules_Issue59291()
+ {
+ var errorMessages = new List();
+ var buildEngine = new Mock();
+ buildEngine.Setup(e => e.LogErrorEvent(It.IsAny()))
+ .Callback(args => errorMessages.Add(args.Message));
+
+ var task = new ApplyCompressionNegotiation
+ {
+ BuildEngine = buildEngine.Object,
+ CandidateAssets = [
+ CreateCandidate(
+ @"D:\work\investigations\aspnet-59291-repro\WebWorkers.Issue4\obj\Release\net9.0\compressed\4t6f7o5fzg-j538s5lvtp.gz",
+ "SpawnDev.BlazorJS.WebWorkers",
+ "Package",
+ "spawndev.blazorjs.webworkers.empty.js.gz",
+ "All",
+ "All",
+ "pjmz1tbrkh",
+ "5QGGFe52U8tL8jO+4zNzscmjjpZSY1o1BGIJkV/RmI4=",
+ @"D:\Nuget\spawndev.blazorjs.webworkers\2.5.30\staticwebassets\spawndev.blazorjs.webworkers.empty.js",
+ "Content-Encoding",
+ "gzip"
+ )],
+ CandidateEndpoints = [.. new StaticWebAssetEndpoint[]
+ {
+ new() {
+ Route = "spawndev.blazorjs.webworkers.empty.js.gz",
+ AssetFile = @"D:\work\investigations\aspnet-59291-repro\RazorClassLibrary1\obj\Release\net9.0\compressed\4t6f7o5fzg-j538s5lvtp.gz",
+ ResponseHeaders = [
+ new StaticWebAssetEndpointResponseHeader { Name = "Accept-Ranges", Value = "bytes" },
+ new StaticWebAssetEndpointResponseHeader { Name = "Cache-Control", Value = "no-cache" },
+ new StaticWebAssetEndpointResponseHeader { Name = "Content-Encoding", Value = "gzip" },
+ new StaticWebAssetEndpointResponseHeader { Name = "Content-Length", Value = "471" },
+ new StaticWebAssetEndpointResponseHeader { Name = "Content-Type", Value = "text/javascript" },
+ new StaticWebAssetEndpointResponseHeader { Name = "ETag", Value = "\u00225QGGFe52U8tL8jO\u002B4zNzscmjjpZSY1o1BGIJkV/RmI4=\u0022" },
+ new StaticWebAssetEndpointResponseHeader { Name = "Last-Modified", Value = "Fri, 09 Feb 2024 02:37:40 GMT" },
+ new StaticWebAssetEndpointResponseHeader { Name = "Vary", Value = "Content-Encoding" }
+ ],
+ EndpointProperties = [
+ new StaticWebAssetEndpointProperty { Name = "integrity", Value = "sha256-5QGGFe52U8tL8jO\u002B4zNzscmjjpZSY1o1BGIJkV/RmI4=" }
+ ],
+ Selectors = []
+ } }.Select(e => e.ToTaskItem())],
+ };
+
+ // Act
+ var result = task.Execute();
+
+ // Assert
+ result.Should().Be(true);
+ }
+
[Fact]
public void AppliesContentNegotiationRules_ProcessesNewCompressedFormatsWhenAvailable()
{