Skip to content

Commit 19d4407

Browse files
committed
Apply API review feedback
1 parent 9d44980 commit 19d4407

15 files changed

+148
-86
lines changed

src/Components/Samples/BlazorUnitedApp/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
app.UseStaticFiles();
2828
app.UseAntiforgery();
2929

30-
app.MapStaticAssetEndpoints();
30+
app.MapStaticAssets();
3131
app.MapRazorComponents<App>()
3232
.AddInteractiveServerRenderMode();
3333

src/Components/WebAssembly/Server/src/Builder/WebAssemblyComponentsEndpointOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public sealed class WebAssemblyComponentsEndpointOptions
3030
/// <summary>
3131
/// Gets or sets the <see cref="string"/> that determines the static assets manifest path mapped to this app.
3232
/// </summary>
33-
public string? AssetsManifestPath { get; set; }
33+
public string? StaticAssetsManifestPath { get; set; }
3434

3535
internal bool ConventionsApplied { get; set; }
3636
}

src/Components/WebAssembly/Server/src/Builder/WebAssemblyRazorComponentsEndpointConventionBuilderExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ public static RazorComponentsEndpointConventionBuilder AddInteractiveWebAssembly
5555

5656
// If the static assets data source for the given manifest name is already added, then just wire-up the Blazor WebAssembly conventions.
5757
// MapStaticWebAssetEndpoints is idempotent and will not add the data source if it already exists.
58-
var staticAssetsManifestPath = options.AssetsManifestPath ?? Path.Combine(AppContext.BaseDirectory, $"{environment.ApplicationName}.staticwebassets.endpoints.json");
58+
var staticAssetsManifestPath = options.StaticAssetsManifestPath ?? Path.Combine(AppContext.BaseDirectory, $"{environment.ApplicationName}.staticwebassets.endpoints.json");
5959
staticAssetsManifestPath = Path.IsPathRooted(staticAssetsManifestPath) ? staticAssetsManifestPath : Path.Combine(AppContext.BaseDirectory, staticAssetsManifestPath);
6060
if (HasStaticAssetDataSource(endpointBuilder, staticAssetsManifestPath))
6161
{
6262
options.ConventionsApplied = true;
63-
endpointBuilder.MapStaticAssetEndpoints(staticAssetsManifestPath)
63+
endpointBuilder.MapStaticAssets(staticAssetsManifestPath)
6464
.AddBlazorWebAssemblyConventions();
6565

6666
return builder;
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#nullable enable
2-
Microsoft.AspNetCore.Components.WebAssembly.Server.WebAssemblyComponentsEndpointOptions.AssetsManifestPath.get -> string?
3-
Microsoft.AspNetCore.Components.WebAssembly.Server.WebAssemblyComponentsEndpointOptions.AssetsManifestPath.set -> void
42
Microsoft.AspNetCore.Components.WebAssembly.Server.WebAssemblyComponentsEndpointOptions.ServeMultithreadingHeaders.get -> bool
53
Microsoft.AspNetCore.Components.WebAssembly.Server.WebAssemblyComponentsEndpointOptions.ServeMultithreadingHeaders.set -> void
4+
Microsoft.AspNetCore.Components.WebAssembly.Server.WebAssemblyComponentsEndpointOptions.StaticAssetsManifestPath.get -> string?
5+
Microsoft.AspNetCore.Components.WebAssembly.Server.WebAssemblyComponentsEndpointOptions.StaticAssetsManifestPath.set -> void

src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
8080

8181
_ = app.UseEndpoints(endpoints =>
8282
{
83-
endpoints.MapStaticAssetEndpoints();
83+
endpoints.MapStaticAssets();
8484
_ = endpoints.MapRazorComponents<TRootComponent>()
8585
.AddAdditionalAssemblies(Assembly.Load("Components.WasmMinimal"))
8686
.AddInteractiveServerRenderMode(options =>

src/Components/test/testassets/Components.TestServer/RemoteAuthenticationStartup.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
2828
app.UseAntiforgery();
2929
app.UseEndpoints(endpoints =>
3030
{
31-
endpoints.MapStaticAssetEndpoints(Path.Combine("trimmed-or-threading", "Components.TestServer", "Components.TestServer.staticwebassets.endpoints.json"));
31+
endpoints.MapStaticAssets(Path.Combine("trimmed-or-threading", "Components.TestServer", "Components.TestServer.staticwebassets.endpoints.json"));
3232
endpoints.MapRazorComponents<RemoteAuthenticationApp>()
3333
.AddAdditionalAssemblies(Assembly.Load("Components.WasmRemoteAuthentication"))
3434
.AddInteractiveWebAssemblyRenderMode(options => options.PathPrefix = "/WasmRemoteAuthentication");

src/Http/Routing/src/Matching/ContentEncodingMetadata.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
namespace Microsoft.AspNetCore.Routing.Matching;
4+
using Microsoft.AspNetCore.Routing.Matching;
5+
6+
namespace Microsoft.AspNetCore.Routing;
57

68
/// <summary>
79
/// Metadata used to negotiate wich endpoint to select based on the value of the Accept-Encoding header.
810
/// </summary>
911
/// <param name="value">The <c>Accept-Encoding</c> value this metadata represents.</param>
1012
/// <param name="quality">The server preference to apply to break ties.</param>
11-
public class ContentEncodingMetadata(string value, double quality) : INegotiateMetadata
13+
public sealed class ContentEncodingMetadata(string value, double quality) : INegotiateMetadata
1214
{
1315
/// <summary>
1416
/// Gets the <c>Accept-Encoding</c> value this metadata represents.

src/Http/Routing/src/PublicAPI.Unshipped.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
#nullable enable
2+
Microsoft.AspNetCore.Routing.ContentEncodingMetadata
3+
Microsoft.AspNetCore.Routing.ContentEncodingMetadata.ContentEncodingMetadata(string! value, double quality) -> void
4+
Microsoft.AspNetCore.Routing.ContentEncodingMetadata.Quality.get -> double
5+
Microsoft.AspNetCore.Routing.ContentEncodingMetadata.Value.get -> string!
26
Microsoft.AspNetCore.Routing.Matching.ContentEncodingMetadata
37
Microsoft.AspNetCore.Routing.Matching.ContentEncodingMetadata.ContentEncodingMetadata(string! value, double quality) -> void
48
Microsoft.AspNetCore.Routing.Matching.ContentEncodingMetadata.Quality.get -> double
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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+
using System.Globalization;
5+
using Microsoft.AspNetCore.StaticAssets;
6+
using Microsoft.Net.Http.Headers;
7+
8+
namespace Microsoft.AspNetCore.Builder;
9+
10+
internal static class StaticAssetDescriptorExtensions
11+
{
12+
internal static long GetContentLength(this StaticAssetDescriptor descriptor)
13+
{
14+
foreach (var header in descriptor.ResponseHeaders)
15+
{
16+
if (header.Name == "Content-Length")
17+
{
18+
return long.Parse(header.Value, CultureInfo.InvariantCulture);
19+
}
20+
}
21+
22+
throw new InvalidOperationException("Content-Length header not found.");
23+
}
24+
25+
internal static DateTimeOffset GetLastModified(this StaticAssetDescriptor descriptor)
26+
{
27+
foreach (var header in descriptor.ResponseHeaders)
28+
{
29+
if (header.Name == "Last-Modified")
30+
{
31+
return DateTimeOffset.Parse(header.Value, CultureInfo.InvariantCulture);
32+
}
33+
}
34+
35+
throw new InvalidOperationException("Last-Modified header not found.");
36+
}
37+
38+
internal static EntityTagHeaderValue GetWeakETag(this StaticAssetDescriptor descriptor)
39+
{
40+
foreach (var header in descriptor.ResponseHeaders)
41+
{
42+
if (header.Name == "ETag")
43+
{
44+
var eTag = EntityTagHeaderValue.Parse(header.Value);
45+
if (eTag.IsWeak)
46+
{
47+
return eTag;
48+
}
49+
}
50+
}
51+
52+
throw new InvalidOperationException("ETag header not found.");
53+
}
54+
55+
internal static bool HasContentEncoding(this StaticAssetDescriptor descriptor)
56+
{
57+
foreach (var selector in descriptor.Selectors)
58+
{
59+
if (selector.Name == "Content-Encoding")
60+
{
61+
return true;
62+
}
63+
}
64+
65+
return false;
66+
}
67+
68+
internal static bool HasETag(this StaticAssetDescriptor descriptor, string tag)
69+
{
70+
foreach (var header in descriptor.ResponseHeaders)
71+
{
72+
if (header.Name == "ETag")
73+
{
74+
var eTag = EntityTagHeaderValue.Parse(header.Value);
75+
if (!eTag.IsWeak && eTag.Tag == tag)
76+
{
77+
return true;
78+
}
79+
}
80+
}
81+
82+
return false;
83+
}
84+
}

src/StaticAssets/src/HotReload/StaticAssetDevelopmentRuntimeHandler.cs renamed to src/StaticAssets/src/Development/StaticAssetDevelopmentRuntimeHandler.cs

Lines changed: 40 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,20 @@
1010
using Microsoft.AspNetCore.Http;
1111
using Microsoft.AspNetCore.Http.Features;
1212
using Microsoft.AspNetCore.Routing;
13+
using Microsoft.AspNetCore.Routing.Patterns;
1314
using Microsoft.AspNetCore.StaticAssets;
1415
using Microsoft.Extensions.Configuration;
1516
using Microsoft.Extensions.DependencyInjection;
1617
using Microsoft.Extensions.FileProviders;
1718
using Microsoft.Extensions.Hosting;
19+
using Microsoft.Extensions.Logging;
1820
using Microsoft.Extensions.Primitives;
1921
using Microsoft.Net.Http.Headers;
2022

2123
namespace Microsoft.AspNetCore.Builder;
2224

2325
// Handles changes during development to support common scenarios where for example, a developer changes a file in the wwwroot folder.
24-
internal class StaticAssetDevelopmentRuntimeHandler(List<StaticAssetDescriptor> descriptors)
26+
internal sealed partial class StaticAssetDevelopmentRuntimeHandler(List<StaticAssetDescriptor> descriptors)
2527
{
2628
public void AttachRuntimePatching(EndpointBuilder builder)
2729
{
@@ -46,7 +48,7 @@ public void AttachRuntimePatching(EndpointBuilder builder)
4648

4749
// In case we were dealing with a compressed asset, we are going to wrap the response body feature to re-compress the asset on the fly.
4850
// and write that to the response instead.
49-
context.Features.Set<IHttpResponseBodyFeature>(new HotReloadStaticAsset(originalFeature, context, asset));
51+
context.Features.Set<IHttpResponseBodyFeature>(new RuntimeStaticAssetResponseBodyFeature(originalFeature, context, asset));
5052
}
5153

5254
await original(context);
@@ -60,13 +62,13 @@ internal static string GetETag(IFileInfo fileInfo)
6062
return $"\"{Convert.ToBase64String(SHA256.HashData(stream))}\"";
6163
}
6264

63-
internal class HotReloadStaticAsset : IHttpResponseBodyFeature
65+
internal sealed class RuntimeStaticAssetResponseBodyFeature : IHttpResponseBodyFeature
6466
{
6567
private readonly IHttpResponseBodyFeature _original;
6668
private readonly HttpContext _context;
6769
private readonly StaticAssetDescriptor _asset;
6870

69-
public HotReloadStaticAsset(IHttpResponseBodyFeature original, HttpContext context, StaticAssetDescriptor asset)
71+
public RuntimeStaticAssetResponseBodyFeature(IHttpResponseBodyFeature original, HttpContext context, StaticAssetDescriptor asset)
7072
{
7173
_original = original;
7274
_context = context;
@@ -172,12 +174,16 @@ internal static void EnableSupport(
172174

173175
if (!disableFallback)
174176
{
177+
var logger = endpoints.ServiceProvider.GetRequiredService<ILogger<StaticAssetDevelopmentRuntimeHandler>>();
178+
175179
// Add a fallback static file handler to serve any file that might have been added after the initial startup.
176-
endpoints.MapFallback(
180+
var fallback = endpoints.MapFallback(
177181
"{**path:file}",
178182
endpoints.CreateApplicationBuilder()
179183
.Use((ctx, nxt) =>
180184
{
185+
Log.StaticAssetNotFoundInManifest(logger, ctx.Request.Path);
186+
181187
ctx.SetEndpoint(null);
182188
ctx.Response.OnStarting((context) =>
183189
{
@@ -197,83 +203,49 @@ internal static void EnableSupport(
197203
})
198204
.UseStaticFiles()
199205
.Build());
200-
}
201-
202-
}
203-
}
204206

205-
internal static class StaticAssetDescriptorExtensions
206-
{
207-
internal static long GetContentLength(this StaticAssetDescriptor descriptor)
208-
{
209-
foreach (var header in descriptor.ResponseHeaders)
210-
{
211-
if (header.Name == "Content-Length")
212-
{
213-
return long.Parse(header.Value, CultureInfo.InvariantCulture);
214-
}
207+
// Set up a custom constraint to only match existing files.
208+
fallback
209+
.Add(endpoint =>
210+
{
211+
if (endpoint is not RouteEndpointBuilder routeEndpoint || routeEndpoint is not { RoutePattern.RawText: { } pattern })
212+
{
213+
return;
214+
}
215+
216+
// Add a custom constraint (not inline) to check if the file exists as part of the route matching
217+
routeEndpoint.RoutePattern = RoutePatternFactory.Parse(
218+
pattern,
219+
null,
220+
new RouteValueDictionary { ["path"] = new FileExistsConstraint(environment) });
221+
});
222+
223+
// Limit matching to supported methods.
224+
fallback.Add(b => b.Metadata.Add(new HttpMethodMetadata(["GET", "HEAD"])));
215225
}
216-
217-
throw new InvalidOperationException("Content-Length header not found.");
218226
}
219227

220-
internal static DateTimeOffset GetLastModified(this StaticAssetDescriptor descriptor)
228+
private static partial class Log
221229
{
222-
foreach (var header in descriptor.ResponseHeaders)
223-
{
224-
if (header.Name == "Last-Modified")
225-
{
226-
return DateTimeOffset.Parse(header.Value, CultureInfo.InvariantCulture);
227-
}
228-
}
230+
private const string StaticAssetNotFoundInManifestMessage = """The static asset '{Path}' was not found in the built time manifest. This file will not be available at runtime if it is not available at compile time during the publish process. If the file was not added to the project during development, and is created at runtime, use the StaticFiles middleware to serve it instead.""";
229231

230-
throw new InvalidOperationException("Last-Modified header not found.");
232+
[LoggerMessage(1, LogLevel.Warning, StaticAssetNotFoundInManifestMessage)]
233+
public static partial void StaticAssetNotFoundInManifest(ILogger logger, string path);
231234
}
232235

233-
internal static EntityTagHeaderValue GetWeakETag(this StaticAssetDescriptor descriptor)
236+
private sealed class FileExistsConstraint(IWebHostEnvironment environment) : IRouteConstraint
234237
{
235-
foreach (var header in descriptor.ResponseHeaders)
236-
{
237-
if (header.Name == "ETag")
238-
{
239-
var eTag = EntityTagHeaderValue.Parse(header.Value);
240-
if (eTag.IsWeak)
241-
{
242-
return eTag;
243-
}
244-
}
245-
}
238+
private readonly IWebHostEnvironment _environment = environment;
246239

247-
throw new InvalidOperationException("ETag header not found.");
248-
}
249-
250-
internal static bool HasContentEncoding(this StaticAssetDescriptor descriptor)
251-
{
252-
foreach (var selector in descriptor.Selectors)
240+
public bool Match(HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
253241
{
254-
if (selector.Name == "Content-Encoding")
242+
if (values[routeKey] is not string path)
255243
{
256-
return true;
244+
return false;
257245
}
258-
}
259246

260-
return false;
261-
}
262-
263-
internal static bool HasETag(this StaticAssetDescriptor descriptor, string tag)
264-
{
265-
foreach (var header in descriptor.ResponseHeaders)
266-
{
267-
if (header.Name == "ETag")
268-
{
269-
var eTag = EntityTagHeaderValue.Parse(header.Value);
270-
if (!eTag.IsWeak && eTag.Tag == tag)
271-
{
272-
return true;
273-
}
274-
}
247+
var fileInfo = _environment.WebRootFileProvider.GetFileInfo(path);
248+
return fileInfo.Exists;
275249
}
276-
277-
return false;
278250
}
279251
}

0 commit comments

Comments
 (0)