| File: ResolveFingerprintedStaticWebAssetEndpointsForAssets.cs | Web Access |
| Project: src\sdk\src\StaticWebAssetsSdk\Tasks\Microsoft.NET.Sdk.StaticWebAssets.Tasks.csproj (Microsoft.NET.Sdk.StaticWebAssets.Tasks) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #nullable disable using Microsoft.AspNetCore.StaticWebAssets.Tasks.Utils; using Microsoft.Build.Framework; namespace Microsoft.AspNetCore.StaticWebAssets.Tasks; // There is a common operation that several targets need to perform when they want to reference endpoints // inside the contents of an application. This can be done by a combination of tasks in the SDK, but it is // cumbersome to do so, specially for third-party targets and SDKs. This task encapsulates the logic to // resolve the preferrred set of endpoints for a given set of assets, taking into account the hosting model // (Standalone or Hosted) and ensuring that fingerprinted endpoints are used when possible. [MSBuildMultiThreadableTask] public class ResolveFingerprintedStaticWebAssetEndpointsForAssets : Task, IMultiThreadableTask { /// <inheritdoc/> public TaskEnvironment TaskEnvironment { get; set; } = TaskEnvironment.Fallback; [Required] public ITaskItem[] CandidateEndpoints { get; set; } [Required] public ITaskItem[] CandidateAssets { get; set; } public bool IsStandalone { get; set; } [Output] public ITaskItem[] ResolvedEndpoints { get; set; } public override bool Execute() { var candidateEndpoints = StaticWebAssetEndpoint.FromItemGroup(CandidateEndpoints); var candidateAssets = StaticWebAsset.FromTaskItemGroup(CandidateAssets, TaskEnvironment); var resolvedEndpoints = new List<StaticWebAssetEndpoint>(); var endpointsByAsset = candidateEndpoints.GroupBy(e => e.AssetFile, OSPath.PathComparer) .ToDictionary(g => g.Key, g => g.ToArray(), OSPath.PathComparer); for (var i = 0; i < candidateAssets.Length; i++) { var asset = candidateAssets[i]; if (!endpointsByAsset.TryGetValue(asset.Identity, out var endpoints)) { Log.LogError($"No endpoint found for asset '{asset.Identity}'"); } else { if (IsStandalone) { var assetPath = asset.ComputeTargetPath("", '/', StaticWebAssetTokenResolver.Instance); var foundMatchingEndpoint = false; for (var j = 0; j < endpoints.Length; j++) { var endpoint = endpoints[j]; if (MatchesAssetPath(asset, assetPath, endpoint)) { foundMatchingEndpoint = true; resolvedEndpoints.Add(endpoint); break; } } if (!foundMatchingEndpoint) { Log.LogError($"No endpoint found for asset '{asset.Identity}' with path '{assetPath}' whose route matches its path."); break; } } else { var foundFingerprintedEndpoint = false; for (var j = 0; j < endpoints.Length; j++) { var endpoint = endpoints[j]; if (HasFingerprint(endpoint)) { foundFingerprintedEndpoint = true; var route = asset.ReplaceTokens(endpoint.Route, StaticWebAssetTokenResolver.Instance); Log.LogMessage(MessageImportance.Low, $"Selected endpoint '{endpoint.Route}' for asset '{asset.Identity}' because it has a fingerprinted route '{route}'."); endpoint.Route = route; resolvedEndpoints.Add(endpoint); break; } } if (!foundFingerprintedEndpoint) { // By definition there's always at least one endpoint for an asset, so we can safely // assume that the first one is the one that should be used when no fingerprinted // endpoint is found. endpoints[0].Route = asset.ReplaceTokens(endpoints[0].Route, StaticWebAssetTokenResolver.Instance); Log.LogMessage(MessageImportance.Low, $"Selected endpoint '{endpoints[0].Route}' for asset '{asset.Identity}' because no fingerprinted endpoint was found."); resolvedEndpoints.Add(endpoints[0]); } } } } ResolvedEndpoints = resolvedEndpoints.Select(e => e.ToTaskItem()).ToArray(); return !Log.HasLoggedErrors; } private static bool HasFingerprint(StaticWebAssetEndpoint endpoint) { for (var i = 0; i < endpoint.EndpointProperties.Length; i++) { var property = endpoint.EndpointProperties[i]; if (string.Equals(property.Name, "fingerprint", StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private bool MatchesAssetPath(StaticWebAsset asset, string assetPath, StaticWebAssetEndpoint endpoint) { var route = asset.ReplaceTokens(endpoint.Route, StaticWebAssetTokenResolver.Instance); if (string.Equals(route, assetPath, StringComparison.OrdinalIgnoreCase)) { Log.LogMessage(MessageImportance.Low, $"Selected endpoint '{endpoint.Route}' for asset '{asset.Identity}' because '{assetPath}' matches resolved route '{route}'."); return true; } else { Log.LogMessage(MessageImportance.Low, $"Skipping endpoint '{endpoint.Route}' for asset '{asset.Identity}' because '{assetPath}' does not match resolved route '{route}'."); return false; } } }