| File: FilterStaticWebAssetEndpoints.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.Build.Framework; namespace Microsoft.AspNetCore.StaticWebAssets.Tasks; // Filters the list of endpoints based on the provided criteria // The endpoints must meet all the criteria to be included. // The criteria are: // - Assets: The asset file that the endpoint is associated with must be in the list of assets. // - Properties: The endpoint must have all the properties specified in the list of properties with // optionally a specific value. // - Selectors: The endpoint must have all the selectors specified in the list of selectors with // optionally a specific value. // - Headers: The endpoint must have all the headers specified in the list of headers with // optionally a specific value. [MSBuildMultiThreadableTask] public class FilterStaticWebAssetEndpoints : Task, IMultiThreadableTask { /// <inheritdoc/> public TaskEnvironment TaskEnvironment { get; set; } = TaskEnvironment.Fallback; public ITaskItem[] Endpoints { get; set; } public ITaskItem[] Assets { get; set; } public ITaskItem[] Filters { get; set; } [Output] public ITaskItem[] FilteredEndpoints { get; set; } [Output] public ITaskItem[] AssetsWithoutMatchingEndpoints { get; set; } public override bool Execute() { var filterCriteria = (Filters ?? []).Select(FilterCriteria.FromTaskItem).ToArray(); var assetFiles = Assets != null ? StaticWebAsset.ToAssetDictionary(Assets, TaskEnvironment) : []; var endpoints = StaticWebAssetEndpoint.FromItemGroup(Endpoints ?? []); var endpointFoundMatchingAsset = new Dictionary<string, StaticWebAsset>(); var filteredEndpoints = new List<StaticWebAssetEndpoint>(); for (var i = 0; i < endpoints.Length; i++) { StaticWebAsset asset = null; var endpoint = endpoints[i]; if (assetFiles.Count > 0 && !assetFiles.TryGetValue(endpoint.AssetFile, out asset)) { continue; } if (MeetsAllCriteria(endpoint, asset, filterCriteria, out var failingCriteria)) { if (asset != null && !endpointFoundMatchingAsset.ContainsKey(asset.Identity)) { endpointFoundMatchingAsset.Add(asset.Identity, asset); } filteredEndpoints.Add(endpoint); } else { Log.LogMessage( MessageImportance.Low, $"Filtered out endpoint {endpoint.Route} because it did not meet the criteria {failingCriteria.Type} {failingCriteria.Name} {failingCriteria.Value}"); } } FilteredEndpoints = filteredEndpoints.Select(e => e.ToTaskItem()).ToArray(); foreach (var asset in endpointFoundMatchingAsset) { assetFiles.Remove(asset.Key); } AssetsWithoutMatchingEndpoints = assetFiles.Values.Select(a => a.ToTaskItem()).ToArray(); return !Log.HasLoggedErrors; } private static bool MeetsAllCriteria(StaticWebAssetEndpoint endpoint, StaticWebAsset asset, FilterCriteria[] filterCriteria, out FilterCriteria failingCriteria) { for (var i = 0; i < filterCriteria.Length; i++) { var criteria = filterCriteria[i]; switch (criteria.Type) { case "Property": var meetsPropertyCriteria = criteria.ExcludeOnMatch(); for (var j = 0; j < endpoint.EndpointProperties.Length; j++) { var property = endpoint.EndpointProperties[j]; if (MeetsCriteria(criteria, property.Name, property.Value)) { meetsPropertyCriteria = !criteria.ExcludeOnMatch(); break; } } if (!meetsPropertyCriteria) { failingCriteria = criteria; return false; } break; case "Selector": var meetsSelectorCriteria = criteria.ExcludeOnMatch(); for (var j = 0; j < endpoint.Selectors.Length; j++) { var selector = endpoint.Selectors[j]; if (MeetsCriteria(criteria, selector.Name, selector.Value)) { meetsSelectorCriteria = !criteria.ExcludeOnMatch(); break; } } if (!meetsSelectorCriteria) { failingCriteria = criteria; return false; } break; case "Header": var meetsHeaderCriteria = criteria.ExcludeOnMatch(); for (var j = 0; j < endpoint.ResponseHeaders.Length; j++) { var header = endpoint.ResponseHeaders[j]; if (MeetsCriteria(criteria, header.Name, header.Value)) { meetsHeaderCriteria = !criteria.ExcludeOnMatch(); break; } } if (!meetsHeaderCriteria) { failingCriteria = criteria; return false; } break; case "Standalone": if (asset == null) { failingCriteria = criteria; return false; } var path = asset.ComputeTargetPath("", '/', StaticWebAssetTokenResolver.Instance); var route = asset.ReplaceTokens(endpoint.Route, StaticWebAssetTokenResolver.Instance); if (!string.Equals(route, path, StringComparison.OrdinalIgnoreCase) || criteria.ExcludeOnMatch()) { failingCriteria = criteria; return false; } break; default: throw new InvalidOperationException($"Unknown criteria type {criteria.Type}"); } } failingCriteria = null; return true; } private static bool MeetsCriteria( FilterCriteria criteria, string name, string value) => string.Equals(name, criteria.Name, StringComparison.OrdinalIgnoreCase) && (string.IsNullOrEmpty(criteria.Value) || string.Equals(value, criteria.Value, StringComparison.Ordinal)); private sealed class FilterCriteria(string type, string name, string value, string mode) { public string Type { get; } = type; public string Name { get; } = name; public string Value { get; } = value; public string Mode { get; } = mode ?? "Include"; public bool ExcludeOnMatch() => string.Equals(Mode, "Exclude", StringComparison.OrdinalIgnoreCase); public static FilterCriteria FromTaskItem(ITaskItem item) => new( item.ItemSpec, item.GetMetadata("Name"), item.GetMetadata("Value"), item.GetMetadata("Mode")); } }