@@ -20,10 +20,10 @@ public class ApplyCompressionNegotiation : Task
20
20
[ Output ]
21
21
public ITaskItem [ ] UpdatedEndpoints { get ; set ; }
22
22
23
- private readonly List < StaticWebAssetEndpointSelector > _selectorsList = new ( ) ;
24
- private readonly List < StaticWebAssetEndpointResponseHeader > _headersList = new ( ) ;
25
- private readonly List < StaticWebAssetEndpointResponseHeader > _tempHeadersList = new ( ) ;
26
- private readonly List < StaticWebAssetEndpointProperty > _propertiesList = new ( ) ;
23
+ private readonly List < StaticWebAssetEndpointSelector > _selectorsList = [ ] ;
24
+ private readonly List < StaticWebAssetEndpointResponseHeader > _headersList = [ ] ;
25
+ private readonly List < StaticWebAssetEndpointResponseHeader > _tempHeadersList = [ ] ;
26
+ private readonly List < StaticWebAssetEndpointProperty > _propertiesList = [ ] ;
27
27
private const int ExpectedCompressionHeadersCount = 2 ;
28
28
29
29
public override bool Execute ( )
@@ -36,7 +36,9 @@ public override bool Execute()
36
36
37
37
var compressionHeadersByEncoding = new Dictionary < string , StaticWebAssetEndpointResponseHeader [ ] > ( ExpectedCompressionHeadersCount ) ;
38
38
39
- ProcessCompressedAssets ( assetsById , endpointsByAsset , updatedEndpoints , compressionHeadersByEncoding ) ;
39
+ using var jsonContext = new JsonWriterContext ( ) ;
40
+
41
+ ProcessCompressedAssets ( assetsById , endpointsByAsset , updatedEndpoints , compressionHeadersByEncoding , jsonContext ) ;
40
42
AddRemainingEndpoints ( endpointsByAsset , updatedEndpoints ) ;
41
43
UpdatedEndpoints = StaticWebAssetEndpoint . ToTaskItems ( updatedEndpoints ) ;
42
44
return true ;
@@ -46,7 +48,8 @@ private void ProcessCompressedAssets(
46
48
Dictionary < string , StaticWebAsset > assetsById ,
47
49
IDictionary < string , List < StaticWebAssetEndpoint > > endpointsByAsset ,
48
50
HashSet < StaticWebAssetEndpoint > updatedEndpoints ,
49
- Dictionary < string , StaticWebAssetEndpointResponseHeader [ ] > compressionHeadersByEncoding )
51
+ Dictionary < string , StaticWebAssetEndpointResponseHeader [ ] > compressionHeadersByEncoding ,
52
+ JsonWriterContext jsonContext )
50
53
{
51
54
foreach ( var compressedAsset in assetsById . Values )
52
55
{
@@ -74,8 +77,7 @@ private void ProcessCompressedAssets(
74
77
StaticWebAssetEndpointResponseHeader . PopulateFromMetadataValue ( compressedEndpoint . ResponseHeadersString , _headersList ) ;
75
78
var currentCompressionHeaders = GetOrCreateCompressionHeaders ( compressionHeadersByEncoding , compressedAsset ) ;
76
79
_headersList . AddRange ( currentCompressionHeaders ) ;
77
- using var headerContext = new JsonWriterContext ( ) ;
78
- var headersString = StaticWebAssetEndpointResponseHeader . ToMetadataValue ( _headersList , headerContext ) ;
80
+ var headersString = StaticWebAssetEndpointResponseHeader . ToMetadataValue ( _headersList , jsonContext ) ;
79
81
compressedEndpoint . SetResponseHeadersString ( headersString ) ;
80
82
}
81
83
@@ -91,14 +93,30 @@ private void ProcessCompressedAssets(
91
93
continue ;
92
94
}
93
95
94
- var endpointCopy = CreateUpdatedEndpoint ( compressedAsset , quality , compressedEndpoint , compressedHeaders , relatedEndpointCandidate ) ;
96
+ var endpointCopy = CreateUpdatedEndpoint ( compressedAsset , quality , compressedEndpoint , compressedHeaders , relatedEndpointCandidate , jsonContext ) ;
95
97
updatedEndpoints . Add ( endpointCopy ) ;
98
+ // Since we are going to remove the endpoints from the associated item group and the route is
99
+ // the ItemSpec, we want to add the original as well so that it gets re-added.
100
+ // The endpoint pointing to the uncompressed asset doesn't have a Content-Encoding selector and
101
+ // will use the default "identity" encoding during content negotiation.
96
102
updatedEndpoints . Add ( relatedEndpointCandidate ) ;
97
103
}
98
104
}
99
105
}
100
106
}
101
107
108
+ // Before we return the updated endpoints we need to capture any other endpoint whose asset is not associated
109
+ // with the compressed asset. This is because we are going to remove the endpoints from the associated item group
110
+ // and the route is the ItemSpec, so it will cause those endpoints to be removed.
111
+ // For example, we have css/app.css and Link/css/app.css where Link=css/app.css and the first asset is a build asset
112
+ // and the second asset is a publish asset.
113
+ // If we are processing build assets, we'll mistakenly remove the endpoints associated with the publish asset.
114
+
115
+ // Iterate over the endpoints and find those endpoints whose route is in the set of updated endpoints but whose asset
116
+ // is not, and add them to the updated endpoints.
117
+
118
+ // Reuse the map we created at the beginning.
119
+ // Remove all the endpoints that were updated to avoid adding them again.
102
120
private void AddRemainingEndpoints (
103
121
IDictionary < string , List < StaticWebAssetEndpoint > > endpointsByAsset ,
104
122
HashSet < StaticWebAssetEndpoint > updatedEndpoints )
@@ -115,6 +133,9 @@ private void AddRemainingEndpoints(
115
133
endpointsByAsset . Remove ( endpoint . AssetFile ) ;
116
134
}
117
135
136
+ // We now have only endpoints that might have the same route but point to different assets
137
+ // and we want to include them in the updated endpoints so that we don't incorrectly remove
138
+ // them from the associated item group when we update the endpoints.
118
139
var endpointsByRoute = GetEndpointsByRoute ( endpointsByAsset ) ;
119
140
var additionalUpdatedEndpoints = new HashSet < StaticWebAssetEndpoint > ( updatedEndpoints . Count , StaticWebAssetEndpoint . RouteAndAssetComparer ) ;
120
141
foreach ( var updatedEndpoint in updatedEndpoints )
@@ -203,7 +224,8 @@ private StaticWebAssetEndpoint CreateUpdatedEndpoint(
203
224
string quality ,
204
225
StaticWebAssetEndpoint compressedEndpoint ,
205
226
HashSet < string > compressedHeaders ,
206
- StaticWebAssetEndpoint relatedEndpointCandidate )
227
+ StaticWebAssetEndpoint relatedEndpointCandidate ,
228
+ JsonWriterContext jsonContext )
207
229
{
208
230
Log . LogMessage ( MessageImportance . Low , "Processing related endpoint '{0}'" , relatedEndpointCandidate . Route ) ;
209
231
var encodingSelector = new StaticWebAssetEndpointSelector
@@ -214,28 +236,24 @@ private StaticWebAssetEndpoint CreateUpdatedEndpoint(
214
236
} ;
215
237
Log . LogMessage ( MessageImportance . Low , " Created Content-Encoding selector for compressed asset '{0}' with size '{1}' is '{2}'" , encodingSelector . Value , encodingSelector . Quality , relatedEndpointCandidate . Route ) ;
216
238
217
- // Build selectors using reusable list to avoid array allocation
218
239
StaticWebAssetEndpointSelector . PopulateFromMetadataValue ( relatedEndpointCandidate . SelectorsString , _selectorsList ) ;
219
240
_selectorsList . Add ( encodingSelector ) ;
220
- using var selectorContext = new JsonWriterContext ( ) ;
221
- var selectorsString = StaticWebAssetEndpointSelector . ToMetadataValue ( _selectorsList , selectorContext ) ;
241
+ var selectorsString = StaticWebAssetEndpointSelector . ToMetadataValue ( _selectorsList , jsonContext ) ;
222
242
223
243
var endpointCopy = new StaticWebAssetEndpoint
224
244
{
225
245
AssetFile = compressedAsset . Identity ,
226
246
Route = relatedEndpointCandidate . Route ,
227
247
} ;
228
248
229
- // Set selectors and properties using string methods to avoid array allocations
230
249
endpointCopy . SetSelectorsString ( selectorsString ) ;
231
250
endpointCopy . SetEndpointPropertiesString ( relatedEndpointCandidate . EndpointPropertiesString ) ;
232
251
233
252
// Build headers using reusable list
234
253
_headersList . Clear ( ) ;
235
254
ApplyCompressedEndpointHeaders ( _headersList , compressedEndpoint , relatedEndpointCandidate . Route ) ;
236
255
ApplyRelatedEndpointCandidateHeaders ( _headersList , relatedEndpointCandidate , compressedHeaders ) ;
237
- using var headerContext = new JsonWriterContext ( ) ;
238
- var headersString = StaticWebAssetEndpointResponseHeader . ToMetadataValue ( _headersList , headerContext ) ;
256
+ var headersString = StaticWebAssetEndpointResponseHeader . ToMetadataValue ( _headersList , jsonContext ) ;
239
257
endpointCopy . SetResponseHeadersString ( headersString ) ;
240
258
241
259
// Update the endpoint
@@ -347,9 +365,9 @@ private void ApplyCompressedEndpointHeaders(List<StaticWebAssetEndpointResponseH
347
365
348
366
private void ApplyRelatedEndpointCandidateHeaders ( List < StaticWebAssetEndpointResponseHeader > headers , StaticWebAssetEndpoint relatedEndpointCandidate , HashSet < string > compressedHeaders )
349
367
{
350
- StaticWebAssetEndpointResponseHeader . PopulateFromMetadataValue ( relatedEndpointCandidate . ResponseHeadersString , _tempHeadersList ) ;
368
+ StaticWebAssetEndpointResponseHeader . PopulateFromMetadataValue ( relatedEndpointCandidate . ResponseHeadersString , _headersList ) ;
351
369
352
- foreach ( var header in _tempHeadersList )
370
+ foreach ( var header in _headersList )
353
371
{
354
372
// We need to keep the headers that are specific to the compressed asset like Content-Length,
355
373
// Last-Modified and ETag. Any other header we should add it.
0 commit comments