From 42cc704249089d4038ac8a9595bb5b0802d4ad53 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Mon, 2 Sep 2024 18:14:55 +0200 Subject: [PATCH] [Static Assets] Improve development experience --- src/StaticAssets/src/BuildAssetMetadata.cs | 6 ++++++ .../StaticAssetDevelopmentRuntimeHandler.cs | 5 ++--- src/StaticAssets/src/LoggerExtensions.cs | 5 +++++ .../src/StaticAssetEndpointDataSource.cs | 7 +++++++ .../src/StaticAssetsEndpointConventionBuilder.cs | 6 +++++- .../StaticAssetsEndpointRouteBuilderExtensions.cs | 8 ++++---- src/StaticAssets/src/StaticAssetsInvoker.cs | 13 ++++++++++++- src/StaticAssets/src/StaticAssetsManifest.cs | 13 +++++++++++-- .../test/StaticAssetsIntegrationTests.cs | 1 + 9 files changed, 53 insertions(+), 11 deletions(-) create mode 100644 src/StaticAssets/src/BuildAssetMetadata.cs diff --git a/src/StaticAssets/src/BuildAssetMetadata.cs b/src/StaticAssets/src/BuildAssetMetadata.cs new file mode 100644 index 000000000000..cdf9fad299d0 --- /dev/null +++ b/src/StaticAssets/src/BuildAssetMetadata.cs @@ -0,0 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.StaticAssets; + +internal sealed class BuildAssetMetadata { } diff --git a/src/StaticAssets/src/Development/StaticAssetDevelopmentRuntimeHandler.cs b/src/StaticAssets/src/Development/StaticAssetDevelopmentRuntimeHandler.cs index c57917d0edb4..f36e9c0915ed 100644 --- a/src/StaticAssets/src/Development/StaticAssetDevelopmentRuntimeHandler.cs +++ b/src/StaticAssets/src/Development/StaticAssetDevelopmentRuntimeHandler.cs @@ -15,7 +15,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; @@ -156,11 +155,11 @@ private static StaticAssetDescriptor FindOriginalAsset(string tag, List(); var explicitlyConfigured = bool.TryParse(config[ReloadStaticAssetsAtRuntimeKey], out var hotReload); - return (!explicitlyConfigured && environment.IsDevelopment()) || (explicitlyConfigured && hotReload); + return (!explicitlyConfigured && isBuildManifest) || (explicitlyConfigured && hotReload); } internal static void EnableSupport( diff --git a/src/StaticAssets/src/LoggerExtensions.cs b/src/StaticAssets/src/LoggerExtensions.cs index ec5a2f47a86a..5308629e8410 100644 --- a/src/StaticAssets/src/LoggerExtensions.cs +++ b/src/StaticAssets/src/LoggerExtensions.cs @@ -59,5 +59,10 @@ public static void FileServed(this ILogger logger, string virtualPath, string ph [LoggerMessage(16, LogLevel.Warning, "The WebRootPath was not found: {WebRootPath}. Static files may be unavailable.", EventName = "WebRootPathNotFound")] public static partial void WebRootPathNotFound(this ILogger logger, string webRootPath); + + [LoggerMessage(17, LogLevel.Warning, + "The application is not running against the published output and Static Web Assets are not enabled." + + " To configure static web assets in other environments, call 'StaticWebAssetsLoader.UseStaticWebAssets(IWebHostEnvironment, IConfiguration)' to enable them.", EventName = "StaticWebAssetsNotEnabled")] + public static partial void EnsureStaticWebAssetsEnabled(this ILogger logger); } diff --git a/src/StaticAssets/src/StaticAssetEndpointDataSource.cs b/src/StaticAssets/src/StaticAssetEndpointDataSource.cs index 0420689dff4c..406d8a9c100f 100644 --- a/src/StaticAssets/src/StaticAssetEndpointDataSource.cs +++ b/src/StaticAssets/src/StaticAssetEndpointDataSource.cs @@ -28,6 +28,7 @@ internal StaticAssetsEndpointDataSource( IServiceProvider serviceProvider, StaticAssetEndpointFactory endpointFactory, string manifestName, + bool isBuildManifest, List descriptors) { ServiceProvider = serviceProvider; @@ -37,8 +38,14 @@ internal StaticAssetsEndpointDataSource( _cancellationTokenSource = new CancellationTokenSource(); _changeToken = new CancellationChangeToken(_cancellationTokenSource.Token); + if (isBuildManifest) + { + _conventions.Add(c => c.Metadata.Add(new BuildAssetMetadata())); + } + DefaultBuilder = new StaticAssetsEndpointConventionBuilder( _lock, + isBuildManifest, descriptors, _conventions, _finallyConventions); diff --git a/src/StaticAssets/src/StaticAssetsEndpointConventionBuilder.cs b/src/StaticAssets/src/StaticAssetsEndpointConventionBuilder.cs index cd5275aff9db..66414fb500a8 100644 --- a/src/StaticAssets/src/StaticAssetsEndpointConventionBuilder.cs +++ b/src/StaticAssets/src/StaticAssetsEndpointConventionBuilder.cs @@ -11,13 +11,15 @@ namespace Microsoft.AspNetCore.StaticAssets; public sealed class StaticAssetsEndpointConventionBuilder : IEndpointConventionBuilder { private readonly object _lck; + private readonly bool _isBuildManifest; private readonly List _descriptors; private readonly List> _conventions; private readonly List> _finallyConventions; - internal StaticAssetsEndpointConventionBuilder(object lck, List descriptors, List> conventions, List> finallyConventions) + internal StaticAssetsEndpointConventionBuilder(object lck, bool isBuildManifest, List descriptors, List> conventions, List> finallyConventions) { _lck = lck; + _isBuildManifest = isBuildManifest; _descriptors = descriptors; _conventions = conventions; _finallyConventions = finallyConventions; @@ -25,6 +27,8 @@ internal StaticAssetsEndpointConventionBuilder(object lck, List Descriptors => _descriptors; + internal bool IsBuildManifest => _isBuildManifest; + /// public void Add(Action convention) { diff --git a/src/StaticAssets/src/StaticAssetsEndpointRouteBuilderExtensions.cs b/src/StaticAssets/src/StaticAssetsEndpointRouteBuilderExtensions.cs index 6d0c8ba0cbe3..bd80b3951e11 100644 --- a/src/StaticAssets/src/StaticAssetsEndpointRouteBuilderExtensions.cs +++ b/src/StaticAssets/src/StaticAssetsEndpointRouteBuilderExtensions.cs @@ -36,7 +36,7 @@ public static StaticAssetsEndpointConventionBuilder MapStaticAssets(this IEndpoi var result = MapStaticAssetsCore(endpoints, staticAssetsManifestPath); - if (StaticAssetDevelopmentRuntimeHandler.IsEnabled(endpoints.ServiceProvider, environment)) + if (StaticAssetDevelopmentRuntimeHandler.IsEnabled(result.IsBuildManifest, endpoints.ServiceProvider)) { StaticAssetDevelopmentRuntimeHandler.EnableSupport(endpoints, result, environment, result.Descriptors); } @@ -56,7 +56,7 @@ private static StaticAssetsEndpointConventionBuilder MapStaticAssetsCore( var manifest = ResolveManifest(manifestPath); - var dataSource = StaticAssetsManifest.CreateDataSource(endpoints, manifestPath, manifest.Endpoints); + var dataSource = StaticAssetsManifest.CreateDataSource(endpoints, manifestPath, manifest.Endpoints, manifest.IsBuildManifest()); return dataSource.DefaultBuilder; } @@ -89,9 +89,9 @@ internal static StaticAssetsEndpointConventionBuilder MapStaticAssets(this IEndp ArgumentNullException.ThrowIfNull(endpoints); var environment = endpoints.ServiceProvider.GetRequiredService(); - var result = StaticAssetsManifest.CreateDataSource(endpoints, "", manifest.Endpoints).DefaultBuilder; + var result = StaticAssetsManifest.CreateDataSource(endpoints, "", manifest.Endpoints, manifest.IsBuildManifest()).DefaultBuilder; - if (StaticAssetDevelopmentRuntimeHandler.IsEnabled(endpoints.ServiceProvider, environment)) + if (StaticAssetDevelopmentRuntimeHandler.IsEnabled(result.IsBuildManifest, endpoints.ServiceProvider)) { StaticAssetDevelopmentRuntimeHandler.EnableSupport(endpoints, result, environment, result.Descriptors); } diff --git a/src/StaticAssets/src/StaticAssetsInvoker.cs b/src/StaticAssets/src/StaticAssetsInvoker.cs index e7f42ca19795..1d21cc2929ec 100644 --- a/src/StaticAssets/src/StaticAssetsInvoker.cs +++ b/src/StaticAssets/src/StaticAssetsInvoker.cs @@ -3,10 +3,13 @@ using System.Diagnostics; using System.Globalization; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Headers; using Microsoft.AspNetCore.Internal; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; @@ -81,7 +84,7 @@ public StaticAssetsInvoker(StaticAssetDescriptor resource, IFileProvider filePro public IFileInfo FileInfo => _fileInfo ??= _fileProvider.GetFileInfo(_resource.AssetPath) is IFileInfo file and { Exists: true } ? file : - throw new InvalidOperationException($"The file '{_resource.AssetPath}' could not be found."); + throw new FileNotFoundException($"The file '{_resource.AssetPath}' could not be found."); private Task ApplyResponseHeadersAsync(StaticAssetInvocationContext context, int statusCode) { @@ -157,6 +160,14 @@ public async Task Invoke(HttpContext context) } catch (FileNotFoundException) { + if (context.GetEndpoint() is Endpoint { Metadata: { } metadata } && metadata.GetMetadata() != null) + { + var environment = context.RequestServices.GetRequiredService(); + if (!environment.IsDevelopment() && environment.WebRootFileProvider is not CompositeFileProvider) + { + _logger.EnsureStaticWebAssetsEnabled(); + } + } context.Response.Clear(); } return; diff --git a/src/StaticAssets/src/StaticAssetsManifest.cs b/src/StaticAssets/src/StaticAssetsManifest.cs index a92526ef5212..9e32c7fc4305 100644 --- a/src/StaticAssets/src/StaticAssetsManifest.cs +++ b/src/StaticAssets/src/StaticAssetsManifest.cs @@ -35,14 +35,23 @@ internal static StaticAssetsManifest Parse(string manifestPath) return result; } - internal static StaticAssetsEndpointDataSource CreateDataSource(IEndpointRouteBuilder endpoints, string manifestName, List descriptors) + internal static StaticAssetsEndpointDataSource CreateDataSource(IEndpointRouteBuilder endpoints, string manifestName, List descriptors, bool isBuildManifest) { - var dataSource = new StaticAssetsEndpointDataSource(endpoints.ServiceProvider, new StaticAssetEndpointFactory(endpoints.ServiceProvider), manifestName, descriptors); + var dataSource = new StaticAssetsEndpointDataSource( + endpoints.ServiceProvider, + new StaticAssetEndpointFactory(endpoints.ServiceProvider), + manifestName, + isBuildManifest, + descriptors); endpoints.DataSources.Add(dataSource); return dataSource; } public int Version { get; set; } + public string ManifestType { get; set; } = ""; + public List Endpoints { get; set; } = []; + + public bool IsBuildManifest() => string.Equals(ManifestType, "Build", StringComparison.OrdinalIgnoreCase); } diff --git a/src/StaticAssets/test/StaticAssetsIntegrationTests.cs b/src/StaticAssets/test/StaticAssetsIntegrationTests.cs index e388094fb533..541a468a15a2 100644 --- a/src/StaticAssets/test/StaticAssetsIntegrationTests.cs +++ b/src/StaticAssets/test/StaticAssetsIntegrationTests.cs @@ -492,6 +492,7 @@ private static void CreateTestManifest(string appName, string webRoot, params Sp var lastModified = DateTimeOffset.UtcNow; File.WriteAllText(filePath, resource.Content); var hash = GetEtag(resource.Content); + manifest.ManifestType = "Build"; manifest.Endpoints.Add(new StaticAssetDescriptor { Route = resource.Path,