Skip to content

[Static Assets] Improve development experience #57671

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/StaticAssets/src/BuildAssetMetadata.cs
Original file line number Diff line number Diff line change
@@ -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 { }
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -156,11 +155,11 @@ private static StaticAssetDescriptor FindOriginalAsset(string tag, List<StaticAs
throw new InvalidOperationException("The original asset was not found.");
}

internal static bool IsEnabled(IServiceProvider serviceProvider, IWebHostEnvironment environment)
internal static bool IsEnabled(bool isBuildManifest, IServiceProvider serviceProvider)
{
var config = serviceProvider.GetRequiredService<IConfiguration>();
var explicitlyConfigured = bool.TryParse(config[ReloadStaticAssetsAtRuntimeKey], out var hotReload);
return (!explicitlyConfigured && environment.IsDevelopment()) || (explicitlyConfigured && hotReload);
return (!explicitlyConfigured && isBuildManifest) || (explicitlyConfigured && hotReload);
}

internal static void EnableSupport(
Expand Down
5 changes: 5 additions & 0 deletions src/StaticAssets/src/LoggerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

7 changes: 7 additions & 0 deletions src/StaticAssets/src/StaticAssetEndpointDataSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ internal StaticAssetsEndpointDataSource(
IServiceProvider serviceProvider,
StaticAssetEndpointFactory endpointFactory,
string manifestName,
bool isBuildManifest,
List<StaticAssetDescriptor> descriptors)
{
ServiceProvider = serviceProvider;
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,24 @@ namespace Microsoft.AspNetCore.StaticAssets;
public sealed class StaticAssetsEndpointConventionBuilder : IEndpointConventionBuilder
{
private readonly object _lck;
private readonly bool _isBuildManifest;
private readonly List<StaticAssetDescriptor> _descriptors;
private readonly List<Action<EndpointBuilder>> _conventions;
private readonly List<Action<EndpointBuilder>> _finallyConventions;

internal StaticAssetsEndpointConventionBuilder(object lck, List<StaticAssetDescriptor> descriptors, List<Action<EndpointBuilder>> conventions, List<Action<EndpointBuilder>> finallyConventions)
internal StaticAssetsEndpointConventionBuilder(object lck, bool isBuildManifest, List<StaticAssetDescriptor> descriptors, List<Action<EndpointBuilder>> conventions, List<Action<EndpointBuilder>> finallyConventions)
{
_lck = lck;
_isBuildManifest = isBuildManifest;
_descriptors = descriptors;
_conventions = conventions;
_finallyConventions = finallyConventions;
}

internal List<StaticAssetDescriptor> Descriptors => _descriptors;

internal bool IsBuildManifest => _isBuildManifest;

/// <inheritdoc/>
public void Add(Action<EndpointBuilder> convention)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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;
}

Expand Down Expand Up @@ -89,9 +89,9 @@ internal static StaticAssetsEndpointConventionBuilder MapStaticAssets(this IEndp
ArgumentNullException.ThrowIfNull(endpoints);

var environment = endpoints.ServiceProvider.GetRequiredService<IWebHostEnvironment>();
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);
}
Expand Down
13 changes: 12 additions & 1 deletion src/StaticAssets/src/StaticAssetsInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -157,6 +160,14 @@ public async Task Invoke(HttpContext context)
}
catch (FileNotFoundException)
{
if (context.GetEndpoint() is Endpoint { Metadata: { } metadata } && metadata.GetMetadata<BuildAssetMetadata>() != null)
{
var environment = context.RequestServices.GetRequiredService<IWebHostEnvironment>();
if (!environment.IsDevelopment() && environment.WebRootFileProvider is not CompositeFileProvider)
{
_logger.EnsureStaticWebAssetsEnabled();
}
}
context.Response.Clear();
}
return;
Expand Down
13 changes: 11 additions & 2 deletions src/StaticAssets/src/StaticAssetsManifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,23 @@ internal static StaticAssetsManifest Parse(string manifestPath)
return result;
}

internal static StaticAssetsEndpointDataSource CreateDataSource(IEndpointRouteBuilder endpoints, string manifestName, List<StaticAssetDescriptor> descriptors)
internal static StaticAssetsEndpointDataSource CreateDataSource(IEndpointRouteBuilder endpoints, string manifestName, List<StaticAssetDescriptor> 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<StaticAssetDescriptor> Endpoints { get; set; } = [];

public bool IsBuildManifest() => string.Equals(ManifestType, "Build", StringComparison.OrdinalIgnoreCase);
}
1 change: 1 addition & 0 deletions src/StaticAssets/test/StaticAssetsIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading