1
1
// Licensed to the .NET Foundation under one or more agreements.
2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
+ using System . Globalization ;
4
5
using Microsoft . Build . Framework ;
6
+ using System . Collections . Concurrent ;
5
7
using Microsoft . NET . Sdk . StaticWebAssets . Tasks ;
6
- using System . Globalization ;
7
8
8
9
namespace Microsoft . AspNetCore . StaticWebAssets . Tasks
9
10
{
@@ -12,7 +13,6 @@ public class DefineStaticWebAssetEndpoints : Task
12
13
[ Required ]
13
14
public ITaskItem [ ] CandidateAssets { get ; set ; }
14
15
15
- [ Required ]
16
16
public ITaskItem [ ] ExistingEndpoints { get ; set ; }
17
17
18
18
[ Required ]
@@ -40,69 +40,91 @@ public override bool Execute()
40
40
}
41
41
}
42
42
43
- var staticWebAssets = CandidateAssets . Select ( StaticWebAsset . FromTaskItem ) . ToDictionary ( a => a . Identity ) ;
44
- var existingEndpoints = StaticWebAssetEndpoint . FromItemGroup ( ExistingEndpoints ) ;
45
- var existingEndpointsByAssetFile = existingEndpoints
46
- . GroupBy ( e => e . AssetFile , OSPath . PathComparer )
47
- . ToDictionary ( g => g . Key , g => new HashSet < StaticWebAssetEndpoint > ( g , StaticWebAssetEndpoint . RouteAndAssetComparer ) ) ;
48
-
49
- var assetsToRemove = new List < string > ( ) ;
50
- foreach ( var kvp in existingEndpointsByAssetFile )
51
- {
52
- var asset = kvp . Key ;
53
- var set = kvp . Value ;
54
- if ( ! staticWebAssets . ContainsKey ( asset ) )
55
- {
56
- assetsToRemove . Remove ( asset ) ;
57
- }
58
- }
59
- foreach ( var asset in assetsToRemove )
60
- {
61
- Log . LogMessage ( MessageImportance . Low , $ "Removing endpoints for asset '{ asset } ' because it no longer exists.") ;
62
- existingEndpointsByAssetFile . Remove ( asset ) ;
63
- }
64
-
43
+ var existingEndpointsByAssetFile = CreateEndpointsByAssetFile ( ) ;
65
44
var contentTypeMappings = ContentTypeMappings . Select ( ContentTypeMapping . FromTaskItem ) . OrderByDescending ( m => m . Priority ) . ToArray ( ) ;
66
45
var contentTypeProvider = new ContentTypeProvider ( contentTypeMappings ) ;
67
- var endpoints = new List < StaticWebAssetEndpoint > ( ) ;
46
+ var endpoints = new ConcurrentBag < StaticWebAssetEndpoint > ( ) ;
68
47
69
- foreach ( var kvp in staticWebAssets )
48
+ Parallel . For ( 0 , CandidateAssets . Length , i =>
70
49
{
71
- var asset = kvp . Value ;
72
-
73
- // StaticWebAssets has this behavior where the base path for an asset only gets applied if the asset comes from a
74
- // package or a referenced project and ignored if it comes from the current project.
75
- // When we define the endpoint, we apply the path to the asset as if it was coming from the current project.
76
- // If the endpoint is then passed to a referencing project or packaged into a nuget package, the path will be
77
- // adjusted at that time.
78
- var assetEndpoints = CreateEndpoints ( asset , contentTypeProvider ) ;
50
+ var asset = StaticWebAsset . FromTaskItem ( CandidateAssets [ i ] ) ;
51
+ var routes = asset . ComputeRoutes ( ) . ToList ( ) ;
79
52
80
- foreach ( var endpoint in assetEndpoints )
53
+ if ( existingEndpointsByAssetFile != null && existingEndpointsByAssetFile . TryGetValue ( asset . Identity , out var set ) )
81
54
{
82
- // Check if the endpoint we are about to define already exists. This can happen during publish as assets defined
83
- // during the build will have already defined endpoints and we only want to add new ones.
84
- if ( existingEndpointsByAssetFile . TryGetValue ( asset . Identity , out var set ) &&
85
- set . TryGetValue ( endpoint , out var existingEndpoint ) )
55
+ for ( var j = routes . Count - 1 ; j >= 0 ; j -- )
86
56
{
87
- Log . LogMessage ( MessageImportance . Low , $ "Skipping asset { asset . Identity } because an endpoint for it already exists at { existingEndpoint . Route } .") ;
88
- continue ;
57
+ var ( label , route , values ) = routes [ j ] ;
58
+ // StaticWebAssets has this behavior where the base path for an asset only gets applied if the asset comes from a
59
+ // package or a referenced project and ignored if it comes from the current project.
60
+ // When we define the endpoint, we apply the path to the asset as if it was coming from the current project.
61
+ // If the endpoint is then passed to a referencing project or packaged into a nuget package, the path will be
62
+ // adjusted at that time.
63
+ var finalRoute = asset . IsProject ( ) || asset . IsPackage ( ) ? StaticWebAsset . Normalize ( Path . Combine ( asset . BasePath , route ) ) : route ;
64
+
65
+ // Check if the endpoint we are about to define already exists. This can happen during publish as assets defined
66
+ // during the build will have already defined endpoints and we only want to add new ones.
67
+ if ( set . Contains ( finalRoute ) )
68
+ {
69
+ Log . LogMessage ( MessageImportance . Low , $ "Skipping asset { asset . Identity } because an endpoint for it already exists at { route } .") ;
70
+ routes . RemoveAt ( j ) ;
71
+ }
89
72
}
73
+ }
90
74
75
+ foreach ( var endpoint in CreateEndpoints ( routes , asset , contentTypeProvider ) )
76
+ {
91
77
Log . LogMessage ( MessageImportance . Low , $ "Adding endpoint { endpoint . Route } for asset { asset . Identity } .") ;
92
78
endpoints . Add ( endpoint ) ;
93
79
}
94
- }
80
+ } ) ;
95
81
96
82
Endpoints = StaticWebAssetEndpoint . ToTaskItems ( endpoints ) ;
97
83
98
84
return ! Log . HasLoggedErrors ;
99
85
}
100
86
101
- private List < StaticWebAssetEndpoint > CreateEndpoints ( StaticWebAsset asset , ContentTypeProvider contentTypeMappings )
87
+ private Dictionary < string , HashSet < string > > CreateEndpointsByAssetFile ( )
88
+ {
89
+ if ( ExistingEndpoints != null && ExistingEndpoints . Length > 0 )
90
+ {
91
+ Dictionary < string , HashSet < string > > existingEndpointsByAssetFile = new ( OSPath . PathComparer ) ;
92
+ var assets = new HashSet < string > ( CandidateAssets . Length , OSPath . PathComparer ) ;
93
+ foreach ( var asset in CandidateAssets )
94
+ {
95
+ assets . Add ( asset . ItemSpec ) ;
96
+ }
97
+
98
+ for ( int i = 0 ; i < ExistingEndpoints . Length ; i ++ )
99
+ {
100
+ var endpointCandidate = ExistingEndpoints [ i ] ;
101
+ var assetFile = endpointCandidate . GetMetadata ( nameof ( StaticWebAssetEndpoint . AssetFile ) ) ;
102
+ if ( ! assets . Contains ( assetFile ) )
103
+ {
104
+ Log . LogMessage ( MessageImportance . Low , $ "Removing endpoints for asset '{ assetFile } ' because it no longer exists.") ;
105
+ continue ;
106
+ }
107
+
108
+ if ( ! existingEndpointsByAssetFile . TryGetValue ( assetFile , out var set ) )
109
+ {
110
+ set = new HashSet < string > ( OSPath . PathComparer ) ;
111
+ existingEndpointsByAssetFile [ assetFile ] = set ;
112
+ }
113
+
114
+ // Add the route
115
+ set . Add ( endpointCandidate . ItemSpec ) ;
116
+ }
117
+
118
+ return existingEndpointsByAssetFile ;
119
+ }
120
+
121
+ return null ;
122
+ }
123
+
124
+ private List < StaticWebAssetEndpoint > CreateEndpoints ( List < StaticWebAsset . StaticWebAssetResolvedRoute > routes , StaticWebAsset asset , ContentTypeProvider contentTypeMappings )
102
125
{
103
- var routes = asset . ComputeRoutes ( ) ;
104
126
var ( length , lastModified ) = ResolveDetails ( asset ) ;
105
- var result = new List < StaticWebAssetEndpoint > ( ) ;
127
+ var result = new List < StaticWebAssetEndpoint > ( ) ;
106
128
foreach ( var ( label , route , values ) in routes )
107
129
{
108
130
var ( mimeType , cacheSetting ) = ResolveContentType ( asset , contentTypeMappings ) ;
0 commit comments