Skip to content

Commit b9891ee

Browse files
authored
[StaticWebAssets] Backport perf fixes (#47689)
1 parent ccd64de commit b9891ee

19 files changed

+309
-99
lines changed

src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.Compression.targets

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ Copyright (c) .NET Foundation. All rights reserved.
223223
CandidateAssets="@(_CompressedStaticWebAssets)"
224224
>
225225
<Output TaskParameter="Assets" ItemName="_CompressionBuildStaticWebAsset" />
226+
<Output TaskParameter="AssetDetails" ItemName="_ResolveBuildCompressedStaticWebAssetsDetails" />
226227
</DefineStaticWebAssets>
227228

228229
<DefineStaticWebAssetEndpoints
@@ -241,6 +242,7 @@ Copyright (c) .NET Foundation. All rights reserved.
241242

242243
<ApplyCompressionNegotiation
243244
CandidateEndpoints="@(StaticWebAssetEndpoint)"
245+
AssetFileDetails="@(_ResolveBuildCompressedStaticWebAssetsDetails)"
244246
CandidateAssets="@(_CompressionCurrentProjectBuildAssets)"
245247
>
246248
<Output TaskParameter="UpdatedEndpoints" ItemName="_UpdatedCompressionBuildEndpoints" />
@@ -313,7 +315,7 @@ Copyright (c) .NET Foundation. All rights reserved.
313315

314316
</ResolveCompressedAssets>
315317

316-
<ItemGroup>
318+
<ItemGroup Condition="'$(IsPackable)' == 'true'">
317319
<_StaticWebAssetExcludedFromPack Include="@(_CompressedStaticWebAssets)" />
318320
</ItemGroup>
319321

src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.Pack.targets

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ Copyright (c) .NET Foundation. All rights reserved.
7575
targets into their packages are expected to disable the generation of $(PackageId).props files and
7676
to manually import build\Microsoft.AspNetCore.StaticWebAssets.props in their custom props files.
7777
-->
78-
<Target Name="GenerateStaticWebAssetsPackFiles" AfterTargets="GenerateStaticWebAssetsManifest">
78+
<Target Name="GenerateStaticWebAssetsPackFiles" Condition="'$(IsPackable)' == 'true'" AfterTargets="GenerateStaticWebAssetsManifest">
7979

8080
<ItemGroup>
8181

src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.Publish.targets

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,16 @@ Copyright (c) .NET Foundation. All rights reserved.
6161
for example, assets from packages as a result of a publish (no-build) invocation. Those assets were already taken
6262
into account when we built the build manifest that we are about to load for resuming the publish process. -->
6363
<PropertyGroup>
64-
<_ShouldLoadBuildManifestAndUpdateAssets>false</_ShouldLoadBuildManifestAndUpdateAssets>
65-
<_ShouldLoadBuildManifestAndUpdateAssets
66-
Condition="@(_CachedBuildStaticWebAssets) == '' or @(_CachedBuildStaticWebAssetDiscoveryPatterns) == '' or @(_CachedBuildStaticWebAssetReferencedProjectsConfiguration) == ''">true</_ShouldLoadBuildManifestAndUpdateAssets>
64+
<_HasStaticWebAssetsProjectReferences Condition="@(ProjectReference) != ''">true</_HasStaticWebAssetsProjectReferences>
65+
<_HasCachedBuildStaticWebAssets Condition="@(_CachedBuildStaticWebAssets) == ''">false</_HasCachedBuildStaticWebAssets>
66+
<_HasCachedBuildStaticWebAssetEndpoints Condition="@(_CachedBuildStaticWebAssetEndpoints) == ''">false</_HasCachedBuildStaticWebAssetEndpoints>
67+
<_HasCachedBuildStaticWebAssetDiscoveryPatterns Condition="@(_CachedBuildStaticWebAssetDiscoveryPatterns) == ''">false</_HasCachedBuildStaticWebAssetDiscoveryPatterns>
68+
<_HasCachedBuildStaticWebAssetReferencedProjectsConfiguration Condition="@(_CachedBuildStaticWebAssetReferencedProjectsConfiguration) == ''">false</_HasCachedBuildStaticWebAssetReferencedProjectsConfiguration>
69+
<_ShouldLoadBuildManifestAndUpdateAssets>false</_ShouldLoadBuildManifestAndUpdateAssets>
70+
<_ShouldLoadBuildManifestAndUpdateAssets Condition="'$(_HasCachedBuildStaticWebAssets)' == 'false' or
71+
'$(_HasCachedBuildStaticWebAssetEndpoints)' == 'false' or
72+
'$(_HasCachedBuildStaticWebAssetDiscoveryPatterns)' == 'false' or
73+
('$(_HasStaticWebAssetsProjectReferences)' == 'true' and '$(_HasCachedBuildStaticWebAssetReferencedProjectsConfiguration)' == 'false')">true</_ShouldLoadBuildManifestAndUpdateAssets>
6774
</PropertyGroup>
6875

6976
<ItemGroup>
@@ -131,11 +138,11 @@ Copyright (c) .NET Foundation. All rights reserved.
131138
<ItemGroup>
132139
<_ReferencedProjectPublishStaticWebAssetsUpdateCandidates
133140
Include="@(_ReferencedProjectPublishStaticWebAssetsItems)"
134-
Condition="'%(_ReferencedProjectPublishStaticWebAssetsItems.ResultType)' == 'StaticWebAsset'" />
141+
Condition="'%(_ReferencedProjectPublishStaticWebAssetsItems.ResultType)' == 'StaticWebAsset'" />
135142

136143
<_ReferencedProjectPublishStaticWebAssetEndpointsUpdateCandidates
137144
Include="@(_ReferencedProjectPublishStaticWebAssetsItems)"
138-
Condition="'%(_ReferencedProjectPublishStaticWebAssetsItems.ResultType)' == 'StaticWebAssetEndpoint'" />
145+
Condition="'%(_ReferencedProjectPublishStaticWebAssetsItems.ResultType)' == 'StaticWebAssetEndpoint'" />
139146
</ItemGroup>
140147

141148
<UpdateExternallyDefinedStaticWebAssets Condition="$(_HasProjectsWithStaticWebAssetPublishTargets)"

src/StaticWebAssetsSdk/Targets/Microsoft.NET.Sdk.StaticWebAssets.targets

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,7 @@ Copyright (c) .NET Foundation. All rights reserved.
437437
<!-- Manifest paths -->
438438
<_StaticWebAssetsManifestBase Condition="'$(_StaticWebAssetsManifestBase)' == ''">$(IntermediateOutputPath)</_StaticWebAssetsManifestBase>
439439
<StaticWebAssetBuildManifestPath>$(_StaticWebAssetsManifestBase)staticwebassets.build.json</StaticWebAssetBuildManifestPath>
440+
<StaticWebAssetsBuildManifestCacheFilePath>$(StaticWebAssetBuildManifestPath).cache</StaticWebAssetsBuildManifestCacheFilePath>
440441
<StaticWebAssetPackManifestPath>$(_StaticWebAssetsManifestBase)staticwebassets.pack.json</StaticWebAssetPackManifestPath>
441442
<StaticWebAssetDevelopmentManifestPath>$(_StaticWebAssetsManifestBase)staticwebassets.development.json</StaticWebAssetDevelopmentManifestPath>
442443
<StaticWebAssetEndpointsBuildManifestPath>$(_StaticWebAssetsManifestBase)staticwebassets.build.endpoints.json</StaticWebAssetEndpointsBuildManifestPath>
@@ -620,26 +621,37 @@ Copyright (c) .NET Foundation. All rights reserved.
620621
DiscoveryPatterns="@(StaticWebAssetDiscoveryPattern)"
621622
Assets="@(StaticWebAsset)"
622623
Endpoints="@(StaticWebAssetEndpoint)"
623-
ManifestPath="$(StaticWebAssetBuildManifestPath)">
624+
ManifestPath="$(StaticWebAssetBuildManifestPath)"
625+
ManifestCacheFilePath="$(StaticWebAssetsBuildManifestCacheFilePath)">
624626
</GenerateStaticWebAssetsManifest>
625627

626628
<GenerateStaticWebAssetEndpointsManifest
627629
Source="$(PackageId)"
628630
ManifestType="Build"
629631
Assets="@(StaticWebAsset)"
630632
Endpoints="@(StaticWebAssetEndpoint)"
631-
ManifestPath="$(StaticWebAssetEndpointsBuildManifestPath)">
633+
ManifestPath="$(StaticWebAssetEndpointsBuildManifestPath)"
634+
CacheFilePath="$(StaticWebAssetBuildManifestPath)">
632635
</GenerateStaticWebAssetEndpointsManifest>
633636

634637
<GenerateStaticWebAssetsDevelopmentManifest
635638
DiscoveryPatterns="@(StaticWebAssetDiscoveryPattern)"
636639
Assets="@(StaticWebAsset)"
637640
Source="$(PackageId)"
638-
ManifestPath="$(StaticWebAssetDevelopmentManifestPath)">
641+
ManifestPath="$(StaticWebAssetDevelopmentManifestPath)"
642+
CacheFilePath="$(StaticWebAssetBuildManifestPath)">
639643
</GenerateStaticWebAssetsDevelopmentManifest>
640644

645+
<ItemGroup>
646+
<_CachedBuildStaticWebAssets Condition="'@(_CachedBuildStaticWebAssets)' == ''" Include="@(StaticWebAsset)" />
647+
<_CachedBuildStaticWebAssetEndpoints Condition="'@(_CachedBuildStaticWebAssetEndpoints)' == ''" Include="@(StaticWebAssetEndpoint)" />
648+
<_CachedBuildStaticWebAssetReferencedProjectsConfiguration Condition="'@(_CachedBuildStaticWebAssetReferencedProjectsConfiguration)' == ''" Include="@(StaticWebAssetProjectConfiguration)" />
649+
<_CachedBuildStaticWebAssetDiscoveryPatterns Condition="'@(_CachedBuildStaticWebAssetDiscoveryPatterns)' == ''" Include="@(StaticWebAssetDiscoveryPattern)" />
650+
</ItemGroup>
651+
641652
<ItemGroup>
642653
<FileWrites Include="$(StaticWebAssetBuildManifestPath)" />
654+
<FileWrites Include="$(StaticWebAssetsBuildManifestCacheFilePath)" />
643655
<FileWrites Include="$(StaticWebAssetDevelopmentManifestPath)" />
644656
<FileWrites Include="$(StaticWebAssetEndpointsBuildManifestPath)" />
645657
</ItemGroup>
@@ -670,10 +682,12 @@ Copyright (c) .NET Foundation. All rights reserved.
670682
BasePath="$(StaticWebAssetBasePath)"
671683
AssetMergeSource="$(StaticWebAssetMergeTarget)">
672684
<Output TaskParameter="Assets" ItemName="StaticWebAsset" />
685+
<Output TaskParameter="AssetDetails" ItemName="_ResolveProjectStaticWebAssetsDetails" />
673686
</DefineStaticWebAssets>
674687

675688
<DefineStaticWebAssetEndpoints
676689
CandidateAssets="@(StaticWebAsset)"
690+
AssetFileDetails="@(_ResolveProjectStaticWebAssetsDetails)"
677691
ExistingEndpoints="@(StaticWebAssetEndpoint)"
678692
ContentTypeMappings="@(StaticWebAssetContentTypeMapping)"
679693
>

src/StaticWebAssetsSdk/Tasks/ApplyCompressionNegotiation.cs

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,27 @@ public class ApplyCompressionNegotiation : Task
1616
[Required]
1717
public ITaskItem[] CandidateAssets { get; set; }
1818

19+
public ITaskItem[] AssetFileDetails { get; set; }
20+
1921
[Output]
2022
public ITaskItem[] UpdatedEndpoints { get; set; }
2123

2224
public Func<string, long> TestResolveFileLength;
2325

26+
private Dictionary<string, ITaskItem> _assetFileDetails;
27+
2428
public override bool Execute()
2529
{
30+
if (AssetFileDetails != null)
31+
{
32+
_assetFileDetails = new(AssetFileDetails.Length, OSPath.PathComparer);
33+
for (int i = 0; i < AssetFileDetails.Length; i++)
34+
{
35+
var item = AssetFileDetails[i];
36+
_assetFileDetails[item.ItemSpec] = item;
37+
}
38+
}
39+
2640
var assetsById = CandidateAssets.Select(StaticWebAsset.FromTaskItem).ToDictionary(a => a.Identity);
2741

2842
var endpointsByAsset = CandidateEndpoints.Select(StaticWebAssetEndpoint.FromTaskItem)
@@ -56,10 +70,6 @@ public override bool Execute()
5670
}
5771

5872
Log.LogMessage("Processing compressed asset: {0}", compressedAsset.Identity);
59-
60-
var length = TestResolveFileLength != null
61-
? TestResolveFileLength(compressedAsset.Identity)
62-
: new FileInfo(compressedAsset.Identity).Length;
6373
StaticWebAssetEndpointResponseHeader[] compressionHeaders = [
6474
new()
6575
{
@@ -73,9 +83,10 @@ public override bool Execute()
7383
}
7484
];
7585

86+
var quality = ResolveQuality(compressedAsset);
7687
foreach (var compressedEndpoint in compressedEndpoints)
7788
{
78-
if (compressedEndpoint.Selectors.Any(s => string.Equals(s.Name,"Content-Encoding", StringComparison.Ordinal)))
89+
if (compressedEndpoint.Selectors.Any(s => string.Equals(s.Name, "Content-Encoding", StringComparison.Ordinal)))
7990
{
8091
Log.LogMessage(MessageImportance.Low, $" Skipping endpoint '{compressedEndpoint.Route}' since it already has a Content-Encoding selector");
8192
continue;
@@ -103,7 +114,7 @@ public override bool Execute()
103114
{
104115
Name = "Content-Encoding",
105116
Value = compressedAsset.AssetTraitValue,
106-
Quality = Math.Round(1.0 / (length + 1), 12).ToString("F12", CultureInfo.InvariantCulture)
117+
Quality = quality
107118
};
108119
Log.LogMessage(MessageImportance.Low, " Created Content-Encoding selector for compressed asset '{0}' with size '{1}' is '{2}'", encodingSelector.Value, encodingSelector.Quality, relatedEndpointCandidate.Route);
109120
var endpointCopy = new StaticWebAssetEndpoint
@@ -202,6 +213,25 @@ public override bool Execute()
202213
return true;
203214
}
204215

216+
private string ResolveQuality(StaticWebAsset compressedAsset)
217+
{
218+
long length;
219+
if(_assetFileDetails != null && _assetFileDetails.TryGetValue(compressedAsset.Identity, out var assetFileDetail))
220+
{
221+
length = long.Parse(assetFileDetail.GetMetadata("FileLength"));
222+
}
223+
else if (TestResolveFileLength != null)
224+
{
225+
length = TestResolveFileLength(compressedAsset.Identity);
226+
}
227+
else
228+
{
229+
length = new FileInfo(compressedAsset.Identity).Length;
230+
}
231+
232+
return Math.Round(1.0 / (length + 1), 12).ToString("F12", CultureInfo.InvariantCulture);
233+
}
234+
205235
private static bool IsCompatible(StaticWebAssetEndpoint compressedEndpoint, StaticWebAssetEndpoint relatedEndpointCandidate)
206236
{
207237
var compressedFingerprint = compressedEndpoint.EndpointProperties.FirstOrDefault(ep => ep.Name == "fingerprint");
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Text.Encodings.Web;
5+
using System.Text.Json;
6+
using System.Text.Json.Serialization;
7+
using Microsoft.NET.Sdk.StaticWebAssets.Tasks;
8+
9+
namespace Microsoft.AspNetCore.StaticWebAssets.Tasks;
10+
11+
[JsonSerializable(typeof(StaticWebAssetsManifest))]
12+
[JsonSerializable(typeof(GenerateStaticWebAssetsDevelopmentManifest.StaticWebAssetsDevelopmentManifest))]
13+
[JsonSerializable(typeof(StaticWebAssetEndpointsManifest))]
14+
public partial class StaticWebAssetsJsonSerializerContext : JsonSerializerContext
15+
{
16+
// Since the manifest is only used at development time, it's ok for it to use the relaxed
17+
// json escaping (which is also what MVC uses by default)
18+
private static readonly JsonSerializerOptions ManifestSerializationOptions = new()
19+
{
20+
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
21+
};
22+
23+
public static readonly StaticWebAssetsJsonSerializerContext RelaxedEscaping = new(ManifestSerializationOptions);
24+
}

src/StaticWebAssetsSdk/Tasks/Data/StaticAssetsManifest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ private string ComputeManifestHash()
8484

8585
public static StaticWebAssetsManifest FromJsonBytes(byte[] jsonBytes)
8686
{
87-
var manifest = JsonSerializer.Deserialize<StaticWebAssetsManifest>(jsonBytes);
87+
var manifest = JsonSerializer.Deserialize(jsonBytes, StaticWebAssetsJsonSerializerContext.RelaxedEscaping.StaticWebAssetsManifest);
8888
if (manifest.Version != 1)
8989
{
9090
throw new InvalidOperationException($"Invalid manifest version. Expected manifest version '1' and found version '{manifest.Version}'.");

src/StaticWebAssetsSdk/Tasks/Data/StaticWebAsset.cs

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Diagnostics;
5+
using System.IO;
56
using System.Security.Cryptography;
67
using System.Security.Principal;
78
using Microsoft.Build.Framework;
@@ -232,11 +233,13 @@ public void ApplyDefaults()
232233

233234
internal static (string fingerprint, string integrity) ComputeFingerprintAndIntegrity(string identity, string originalItemSpec)
234235
{
235-
using var file = File.Exists(identity) ?
236-
File.OpenRead(identity) :
237-
(File.Exists(originalItemSpec) ?
238-
File.OpenRead(originalItemSpec) :
239-
throw new InvalidOperationException($"No file exists for the asset at either location '{identity}' or '{originalItemSpec}'."));
236+
var fileInfo = ResolveFile(identity, originalItemSpec);
237+
return ComputeFingerprintAndIntegrity(fileInfo);
238+
}
239+
240+
internal static (string fingerprint, string integrity) ComputeFingerprintAndIntegrity(FileInfo fileInfo)
241+
{
242+
using var file = fileInfo.OpenRead();
240243

241244
#if NET6_0_OR_GREATER
242245
var hash = SHA256.HashData(file);
@@ -249,11 +252,14 @@ internal static (string fingerprint, string integrity) ComputeFingerprintAndInte
249252

250253
internal static string ComputeIntegrity(string identity, string originalItemSpec)
251254
{
252-
using var file = File.Exists(identity) ?
253-
File.OpenRead(identity) :
254-
(File.Exists(originalItemSpec) ?
255-
File.OpenRead(originalItemSpec) :
256-
throw new InvalidOperationException($"No file exists for the asset at either location '{identity}' or '{originalItemSpec}'."));
255+
var fileInfo = ResolveFile(identity, originalItemSpec);
256+
return ComputeIntegrity(fileInfo);
257+
}
258+
259+
internal static string ComputeIntegrity(FileInfo fileInfo)
260+
{
261+
using var file = fileInfo.OpenRead();
262+
257263
#if NET6_0_OR_GREATER
258264
var hash = SHA256.HashData(file);
259265
#else
@@ -916,6 +922,24 @@ internal string EmbedTokens(string relativePath)
916922
return pattern.RawPattern;
917923
}
918924

925+
internal FileInfo ResolveFile() => ResolveFile(Identity, OriginalItemSpec);
926+
927+
internal static FileInfo ResolveFile(string identity, string originalItemSpec)
928+
{
929+
var fileInfo = new FileInfo(identity);
930+
if (fileInfo.Exists)
931+
{
932+
return fileInfo;
933+
}
934+
fileInfo = new FileInfo(originalItemSpec);
935+
if (fileInfo.Exists)
936+
{
937+
return fileInfo;
938+
}
939+
940+
throw new InvalidOperationException($"No file exists for the asset at either location '{identity}' or '{originalItemSpec}'.");
941+
}
942+
919943
[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
920944
internal class StaticWebAssetResolvedRoute(string pathLabel, string path, Dictionary<string, string> tokens)
921945
{

src/StaticWebAssetsSdk/Tasks/Data/StaticWebAssetEndpointProperty.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
54
using System.Diagnostics;
65
using System.Text.Json;
6+
using System.Text.Json.Serialization.Metadata;
7+
using Microsoft.AspNetCore.StaticWebAssets.Tasks;
78

89
namespace Microsoft.NET.Sdk.StaticWebAssets.Tasks;
910

1011
[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
1112
public class StaticWebAssetEndpointProperty : IComparable<StaticWebAssetEndpointProperty>, IEquatable<StaticWebAssetEndpointProperty>
1213
{
14+
private static readonly JsonTypeInfo<StaticWebAssetEndpointProperty[]> _jsonTypeInfo =
15+
StaticWebAssetsJsonSerializerContext.Default.StaticWebAssetEndpointPropertyArray;
16+
1317
public string Name { get; set; }
1418

1519
public string Value { get; set; }
1620

1721
internal static StaticWebAssetEndpointProperty[] FromMetadataValue(string value)
1822
{
19-
return string.IsNullOrEmpty(value) ? [] : JsonSerializer.Deserialize<StaticWebAssetEndpointProperty[]>(value);
23+
return string.IsNullOrEmpty(value) ? [] : JsonSerializer.Deserialize(value, _jsonTypeInfo);
2024
}
2125

2226
internal static string ToMetadataValue(StaticWebAssetEndpointProperty[] responseHeaders)

src/StaticWebAssetsSdk/Tasks/Data/StaticWebAssetEndpointResponseHeader.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
54
using System.Diagnostics;
65
using System.Text.Json;
6+
using System.Text.Json.Serialization.Metadata;
7+
using Microsoft.AspNetCore.StaticWebAssets.Tasks;
78

89
namespace Microsoft.NET.Sdk.StaticWebAssets.Tasks;
910

1011
[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
1112
public class StaticWebAssetEndpointResponseHeader : IEquatable<StaticWebAssetEndpointResponseHeader>, IComparable<StaticWebAssetEndpointResponseHeader>
1213
{
14+
private static readonly JsonTypeInfo<StaticWebAssetEndpointResponseHeader[]> _jsonTypeInfo =
15+
StaticWebAssetsJsonSerializerContext.Default.StaticWebAssetEndpointResponseHeaderArray;
16+
1317
public string Name { get; set; }
1418

1519
public string Value { get; set; }
1620

1721
internal static StaticWebAssetEndpointResponseHeader[] FromMetadataValue(string value)
1822
{
19-
return string.IsNullOrEmpty(value) ? [] : JsonSerializer.Deserialize<StaticWebAssetEndpointResponseHeader[]>(value);
23+
return string.IsNullOrEmpty(value) ? [] : JsonSerializer.Deserialize(value, _jsonTypeInfo);
2024
}
2125

2226
internal static string ToMetadataValue(StaticWebAssetEndpointResponseHeader[] responseHeaders)

0 commit comments

Comments
 (0)