Skip to content

Commit fc4f881

Browse files
[Static Assets] Improve development experience (#57764)
Backport of #57671 to release/9.0 /cc @javiercn # [Static Assets] Improve development experience Fixes an issue when running a published app on Development and provides a better error when static web assets are not enabled during development ## Description - Addresses two issues that we found during development: - When the user publishes the app and runs against the `Development` environment: - Some logic that is pivoted on based on the Environment and is only used during development (Build as opposed to Publish) interferes with the output and causes issues on the running app. - When the app runs during development, but Static Web Assets are not enabled for some reason: - The app isn't able to find some static files and errors out. - The fix improves the experience by detecting this situation and logging a warning on the console with instructions on how to enable static web assets explicitly. ## Customer Impact * Running in Docker 'Regular Mode' in Visual Studio breaks as the app runs the published output with `Development` environment. * Running the app during development with a custom environment or without a launch profile will cause some static files to not be found. ## Regression? - [X] Yes - [ ] No 8.0 and earlier, the published output was runnable in `Development` environment. For example in Docker containers regular mode (how the issue was found). ## Risk - [ ] High - [ ] Medium - [X] Low The changes uses information that we produce during the build (Build and Publish) to differentiate between the two scenarios and disable the development functionality for the published output and provide a better error message when static web assets are not enabled during development. ## Verification - [X] Manual (required) - [ ] Automated ## Packaging changes reviewed? - [ ] Yes - [ ] No - [X] N/A ---- ## When servicing release/2.1 - [ ] Make necessary changes in eng/PatchConfig.props
1 parent 59f3b23 commit fc4f881

9 files changed

+53
-11
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.StaticAssets;
5+
6+
internal sealed class BuildAssetMetadata { }

src/StaticAssets/src/Development/StaticAssetDevelopmentRuntimeHandler.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
using Microsoft.Extensions.Configuration;
1616
using Microsoft.Extensions.DependencyInjection;
1717
using Microsoft.Extensions.FileProviders;
18-
using Microsoft.Extensions.Hosting;
1918
using Microsoft.Extensions.Logging;
2019
using Microsoft.Extensions.Primitives;
2120
using Microsoft.Net.Http.Headers;
@@ -156,11 +155,11 @@ private static StaticAssetDescriptor FindOriginalAsset(string tag, List<StaticAs
156155
throw new InvalidOperationException("The original asset was not found.");
157156
}
158157

159-
internal static bool IsEnabled(IServiceProvider serviceProvider, IWebHostEnvironment environment)
158+
internal static bool IsEnabled(bool isBuildManifest, IServiceProvider serviceProvider)
160159
{
161160
var config = serviceProvider.GetRequiredService<IConfiguration>();
162161
var explicitlyConfigured = bool.TryParse(config[ReloadStaticAssetsAtRuntimeKey], out var hotReload);
163-
return (!explicitlyConfigured && environment.IsDevelopment()) || (explicitlyConfigured && hotReload);
162+
return (!explicitlyConfigured && isBuildManifest) || (explicitlyConfigured && hotReload);
164163
}
165164

166165
internal static void EnableSupport(

src/StaticAssets/src/LoggerExtensions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,10 @@ public static void FileServed(this ILogger logger, string virtualPath, string ph
5959
[LoggerMessage(16, LogLevel.Warning,
6060
"The WebRootPath was not found: {WebRootPath}. Static files may be unavailable.", EventName = "WebRootPathNotFound")]
6161
public static partial void WebRootPathNotFound(this ILogger logger, string webRootPath);
62+
63+
[LoggerMessage(17, LogLevel.Warning,
64+
"The application is not running against the published output and Static Web Assets are not enabled." +
65+
" To configure static web assets in other environments, call 'StaticWebAssetsLoader.UseStaticWebAssets(IWebHostEnvironment, IConfiguration)' to enable them.", EventName = "StaticWebAssetsNotEnabled")]
66+
public static partial void EnsureStaticWebAssetsEnabled(this ILogger logger);
6267
}
6368

src/StaticAssets/src/StaticAssetEndpointDataSource.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ internal StaticAssetsEndpointDataSource(
2828
IServiceProvider serviceProvider,
2929
StaticAssetEndpointFactory endpointFactory,
3030
string manifestName,
31+
bool isBuildManifest,
3132
List<StaticAssetDescriptor> descriptors)
3233
{
3334
ServiceProvider = serviceProvider;
@@ -37,8 +38,14 @@ internal StaticAssetsEndpointDataSource(
3738
_cancellationTokenSource = new CancellationTokenSource();
3839
_changeToken = new CancellationChangeToken(_cancellationTokenSource.Token);
3940

41+
if (isBuildManifest)
42+
{
43+
_conventions.Add(c => c.Metadata.Add(new BuildAssetMetadata()));
44+
}
45+
4046
DefaultBuilder = new StaticAssetsEndpointConventionBuilder(
4147
_lock,
48+
isBuildManifest,
4249
descriptors,
4350
_conventions,
4451
_finallyConventions);

src/StaticAssets/src/StaticAssetsEndpointConventionBuilder.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,24 @@ namespace Microsoft.AspNetCore.StaticAssets;
1111
public sealed class StaticAssetsEndpointConventionBuilder : IEndpointConventionBuilder
1212
{
1313
private readonly object _lck;
14+
private readonly bool _isBuildManifest;
1415
private readonly List<StaticAssetDescriptor> _descriptors;
1516
private readonly List<Action<EndpointBuilder>> _conventions;
1617
private readonly List<Action<EndpointBuilder>> _finallyConventions;
1718

18-
internal StaticAssetsEndpointConventionBuilder(object lck, List<StaticAssetDescriptor> descriptors, List<Action<EndpointBuilder>> conventions, List<Action<EndpointBuilder>> finallyConventions)
19+
internal StaticAssetsEndpointConventionBuilder(object lck, bool isBuildManifest, List<StaticAssetDescriptor> descriptors, List<Action<EndpointBuilder>> conventions, List<Action<EndpointBuilder>> finallyConventions)
1920
{
2021
_lck = lck;
22+
_isBuildManifest = isBuildManifest;
2123
_descriptors = descriptors;
2224
_conventions = conventions;
2325
_finallyConventions = finallyConventions;
2426
}
2527

2628
internal List<StaticAssetDescriptor> Descriptors => _descriptors;
2729

30+
internal bool IsBuildManifest => _isBuildManifest;
31+
2832
/// <inheritdoc/>
2933
public void Add(Action<EndpointBuilder> convention)
3034
{

src/StaticAssets/src/StaticAssetsEndpointRouteBuilderExtensions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public static StaticAssetsEndpointConventionBuilder MapStaticAssets(this IEndpoi
3636

3737
var result = MapStaticAssetsCore(endpoints, staticAssetsManifestPath);
3838

39-
if (StaticAssetDevelopmentRuntimeHandler.IsEnabled(endpoints.ServiceProvider, environment))
39+
if (StaticAssetDevelopmentRuntimeHandler.IsEnabled(result.IsBuildManifest, endpoints.ServiceProvider))
4040
{
4141
StaticAssetDevelopmentRuntimeHandler.EnableSupport(endpoints, result, environment, result.Descriptors);
4242
}
@@ -56,7 +56,7 @@ private static StaticAssetsEndpointConventionBuilder MapStaticAssetsCore(
5656

5757
var manifest = ResolveManifest(manifestPath);
5858

59-
var dataSource = StaticAssetsManifest.CreateDataSource(endpoints, manifestPath, manifest.Endpoints);
59+
var dataSource = StaticAssetsManifest.CreateDataSource(endpoints, manifestPath, manifest.Endpoints, manifest.IsBuildManifest());
6060
return dataSource.DefaultBuilder;
6161
}
6262

@@ -89,9 +89,9 @@ internal static StaticAssetsEndpointConventionBuilder MapStaticAssets(this IEndp
8989
ArgumentNullException.ThrowIfNull(endpoints);
9090

9191
var environment = endpoints.ServiceProvider.GetRequiredService<IWebHostEnvironment>();
92-
var result = StaticAssetsManifest.CreateDataSource(endpoints, "", manifest.Endpoints).DefaultBuilder;
92+
var result = StaticAssetsManifest.CreateDataSource(endpoints, "", manifest.Endpoints, manifest.IsBuildManifest()).DefaultBuilder;
9393

94-
if (StaticAssetDevelopmentRuntimeHandler.IsEnabled(endpoints.ServiceProvider, environment))
94+
if (StaticAssetDevelopmentRuntimeHandler.IsEnabled(result.IsBuildManifest, endpoints.ServiceProvider))
9595
{
9696
StaticAssetDevelopmentRuntimeHandler.EnableSupport(endpoints, result, environment, result.Descriptors);
9797
}

src/StaticAssets/src/StaticAssetsInvoker.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33

44
using System.Diagnostics;
55
using System.Globalization;
6+
using Microsoft.AspNetCore.Hosting;
67
using Microsoft.AspNetCore.Http;
78
using Microsoft.AspNetCore.Http.Headers;
89
using Microsoft.AspNetCore.Internal;
10+
using Microsoft.Extensions.DependencyInjection;
911
using Microsoft.Extensions.FileProviders;
12+
using Microsoft.Extensions.Hosting;
1013
using Microsoft.Extensions.Logging;
1114
using Microsoft.Net.Http.Headers;
1215

@@ -81,7 +84,7 @@ public StaticAssetsInvoker(StaticAssetDescriptor resource, IFileProvider filePro
8184
public IFileInfo FileInfo => _fileInfo ??=
8285
_fileProvider.GetFileInfo(_resource.AssetPath) is IFileInfo file and { Exists: true } ?
8386
file :
84-
throw new InvalidOperationException($"The file '{_resource.AssetPath}' could not be found.");
87+
throw new FileNotFoundException($"The file '{_resource.AssetPath}' could not be found.");
8588

8689
private Task ApplyResponseHeadersAsync(StaticAssetInvocationContext context, int statusCode)
8790
{
@@ -157,6 +160,14 @@ public async Task Invoke(HttpContext context)
157160
}
158161
catch (FileNotFoundException)
159162
{
163+
if (context.GetEndpoint() is Endpoint { Metadata: { } metadata } && metadata.GetMetadata<BuildAssetMetadata>() != null)
164+
{
165+
var environment = context.RequestServices.GetRequiredService<IWebHostEnvironment>();
166+
if (!environment.IsDevelopment() && environment.WebRootFileProvider is not CompositeFileProvider)
167+
{
168+
_logger.EnsureStaticWebAssetsEnabled();
169+
}
170+
}
160171
context.Response.Clear();
161172
}
162173
return;

src/StaticAssets/src/StaticAssetsManifest.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,23 @@ internal static StaticAssetsManifest Parse(string manifestPath)
3535
return result;
3636
}
3737

38-
internal static StaticAssetsEndpointDataSource CreateDataSource(IEndpointRouteBuilder endpoints, string manifestName, List<StaticAssetDescriptor> descriptors)
38+
internal static StaticAssetsEndpointDataSource CreateDataSource(IEndpointRouteBuilder endpoints, string manifestName, List<StaticAssetDescriptor> descriptors, bool isBuildManifest)
3939
{
40-
var dataSource = new StaticAssetsEndpointDataSource(endpoints.ServiceProvider, new StaticAssetEndpointFactory(endpoints.ServiceProvider), manifestName, descriptors);
40+
var dataSource = new StaticAssetsEndpointDataSource(
41+
endpoints.ServiceProvider,
42+
new StaticAssetEndpointFactory(endpoints.ServiceProvider),
43+
manifestName,
44+
isBuildManifest,
45+
descriptors);
4146
endpoints.DataSources.Add(dataSource);
4247
return dataSource;
4348
}
4449

4550
public int Version { get; set; }
4651

52+
public string ManifestType { get; set; } = "";
53+
4754
public List<StaticAssetDescriptor> Endpoints { get; set; } = [];
55+
56+
public bool IsBuildManifest() => string.Equals(ManifestType, "Build", StringComparison.OrdinalIgnoreCase);
4857
}

src/StaticAssets/test/StaticAssetsIntegrationTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,7 @@ private static void CreateTestManifest(string appName, string webRoot, params Sp
492492
var lastModified = DateTimeOffset.UtcNow;
493493
File.WriteAllText(filePath, resource.Content);
494494
var hash = GetEtag(resource.Content);
495+
manifest.ManifestType = "Build";
495496
manifest.Endpoints.Add(new StaticAssetDescriptor
496497
{
497498
Route = resource.Path,

0 commit comments

Comments
 (0)