| File: GenerateStaticWebAssetEndpointsPropsFile.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 System.Security.Cryptography; using System.Xml; using Microsoft.Build.Framework; namespace Microsoft.AspNetCore.StaticWebAssets.Tasks; [MSBuildMultiThreadableTask] public class GenerateStaticWebAssetEndpointsPropsFile : Task, IMultiThreadableTask { [Required] public string TargetPropsFilePath { get; set; } public string PackagePathPrefix { get; set; } = "staticwebassets"; [Required] public ITaskItem[] StaticWebAssets { get; set; } [Required] public ITaskItem[] StaticWebAssetEndpoints { get; set; } public TaskEnvironment TaskEnvironment { get; set; } = TaskEnvironment.Fallback; public override bool Execute() { var endpoints = StaticWebAssetEndpoint.FromItemGroup(StaticWebAssetEndpoints); var assets = StaticWebAsset.ToAssetDictionary(StaticWebAssets, TaskEnvironment); if (!ValidateArguments(endpoints, assets)) { return false; } return ExecuteCore(endpoints, assets); } private bool ExecuteCore(StaticWebAssetEndpoint[] endpoints, Dictionary<string, StaticWebAsset> assets) { if (endpoints.Length == 0) { return !Log.HasLoggedErrors; } var itemGroup = new XElement("ItemGroup"); var orderedAssets = endpoints.OrderBy(e => e.Route, StringComparer.OrdinalIgnoreCase) .ThenBy(e => e.AssetFile, StringComparer.OrdinalIgnoreCase); foreach (var element in orderedAssets) { var asset = assets[element.AssetFile]; var path = asset.ReplaceTokens(asset.RelativePath, StaticWebAssetTokenResolver.Instance, TokenResolveMode.Pack); var fullPathExpression = $"""$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\{StaticWebAsset.Normalize(PackagePathPrefix)}\{StaticWebAsset.Normalize(path).Replace("/", "\\")}'))"""; itemGroup.Add(new XElement(nameof(StaticWebAssetEndpoint), new XAttribute("Include", element.Route), new XElement(nameof(StaticWebAssetEndpoint.AssetFile), fullPathExpression), new XElement(nameof(StaticWebAssetEndpoint.Selectors), new XCData(StaticWebAssetEndpointSelector.ToMetadataValue(element.Selectors))), new XElement(nameof(StaticWebAssetEndpoint.EndpointProperties), new XCData(StaticWebAssetEndpointProperty.ToMetadataValue(element.EndpointProperties))), new XElement(nameof(StaticWebAssetEndpoint.ResponseHeaders), new XCData(StaticWebAssetEndpointResponseHeader.ToMetadataValue(element.ResponseHeaders))))); } var document = new XDocument(new XDeclaration("1.0", "utf-8", "yes")); var root = new XElement("Project", itemGroup); document.Add(root); var settings = new XmlWriterSettings { Encoding = Encoding.UTF8, CloseOutput = false, OmitXmlDeclaration = true, Indent = true, NewLineOnAttributes = false, Async = true }; using var memoryStream = new MemoryStream(); using (var xmlWriter = XmlWriter.Create(memoryStream, settings)) { document.WriteTo(xmlWriter); } var data = memoryStream.ToArray(); WriteFile(data); return !Log.HasLoggedErrors; } private void WriteFile(byte[] data) { var targetPropsFilePath = string.IsNullOrWhiteSpace(TargetPropsFilePath) ? TargetPropsFilePath : TaskEnvironment.GetAbsolutePath(TargetPropsFilePath).Value; var dataHash = ComputeHash(data); var fileExists = File.Exists(targetPropsFilePath); var existingFileHash = fileExists ? ComputeHash(File.ReadAllBytes(targetPropsFilePath)) : ""; if (!fileExists) { Log.LogMessage(MessageImportance.Low, $"Creating file '{TargetPropsFilePath}' does not exist."); File.WriteAllBytes(targetPropsFilePath, data); } else if (!string.Equals(dataHash, existingFileHash, StringComparison.Ordinal)) { Log.LogMessage(MessageImportance.Low, $"Updating '{TargetPropsFilePath}' file because the hash '{dataHash}' is different from existing file hash '{existingFileHash}'."); File.WriteAllBytes(targetPropsFilePath, data); } else { Log.LogMessage(MessageImportance.Low, $"Skipping file update because the hash '{dataHash}' has not changed."); } } private static string ComputeHash(byte[] data) { #if !NET9_0_OR_GREATER using var sha256 = SHA256.Create(); var result = sha256.ComputeHash(data); #else var result = SHA256.HashData(data); #endif return Convert.ToBase64String(result); } private bool ValidateArguments(StaticWebAssetEndpoint[] endpoints, Dictionary<string, StaticWebAsset> asset) { var valid = true; foreach (var endpoint in endpoints) { if (!asset.ContainsKey(endpoint.AssetFile)) { Log.LogError($"The asset file '{endpoint.AssetFile}' specified in the endpoint '{endpoint.Route}' does not exist."); valid = false; } } return valid; } }