-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Description
Description
These set of APIs allow Blazor, MVC and Razor pages to map well-known URLs to content-specific (fingerprinted URLs).
Scenarios
Startup/Program APIs
The list of assets is defined on a per-endpoint basis. MVC and RazorPages opt-in to the feature via a call to WithResourceCollection following the call to MapRazorPages() or MapControllers().
This call adds a ResourceAssetCollection to the Metadata collection of the endpoints, which contains the mapping between human-readable URLs and content-specific URLs.
From there, other components can retrieve the mapping from endpoint metadata.
app.MapStaticAssets();
app.MapRazorPages()
+ .WithResourceCollection();Since we can have calls to map different manifests, WithResourceCollection takes a parameter to provide the Id for the matching MatchStaticAssets.
app.MapStaticAssets("WebassemblyApp1.Client");
app.MapStaticAssets("WebassemblyApp2.Client");
app.MapRazorPages()
+ .WithResourceCollection("WebassemblyApp1.Client");Consuming the mapped assets
The assets are consumed indirectly from MVC and Razor Pages via existing Script, Image, Link, and Url tag helpers.
In Blazor, the assets are consumed via the Assets property in ComponentBase which exposes an indexer to resolve the fingerprinted url for a given asset.
<link rel="stylesheet" href="@Assets["app.css"]" />
<link rel="stylesheet" href="@Assets["BlazorWeb-CSharp.styles.css"]" />This in the future will be cleaner with a compiler feature similar to the Url tag helper in MVC that transforms "~/app.css" into the above code inside the href attribute.
In addition to this, there are two built-in components to generate an importmap for scripts. See here for details.
TL;DR: Creates a mapping for calls to import and script type="module" that optionally includes integrity information. A sample:
<script type="importmap">
{
"imports": {
"square": "./module/shapes/square.js"
},
"integrity": {
"./module/shapes/square.js": "sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
}
}
</script>There is an ImportMap blazor component
<ImportMap />and in MVC we extended the script tag helper to support it
<script type="importmap"></script>Layering
I included this section to explain the reasons for some of the APIs below.
flowchart LR
subgraph AspNet[ASP.NET Framework reference]
StaticAssets[Static Assets]
Endpoints[Components Endpoints SSR]
MVCRp[Mvc and Razor Pages]
end
Components[Components]
ComponentsWasm[Components Webassembly]
ComponentsWebView[Components WebView]
ComponentsWasm-->Components
ComponentsWebView-->Components
Endpoints-->Components
Endpoints-->StaticAssets
MVCRp-->Endpoints
Two important things:
- Components doesn't have a reference to ASP.NET Core because it runs in other environments, and is what Razor Class Libraries target.
- MVC and Razor Pages consume the SSR support for components.
With this in mind, information for the assets is exposed differently from StaticAssets and Components
flowchart TD
subgraph AspNet[ASP.NET Framework reference]
subgraph StaticAssets[Static Assets]
StaticAssetDescriptor
StaticAssetSelector
StaticAssetProperty
StaticAssetResponseHeader
end
Endpoints[Components Endpoints SSR]
MVCRp[Mvc and Razor Pages]
end
subgraph Components[Components]
ResourceAssetCollection
ResourceAsset
ResourceAssetProperty
ImportMapDefinition
end
ComponentsWasm[Components Webassembly]
ComponentsWebView[Components WebView]
ComponentsWasm-->Components
ComponentsWebView-->Components
Endpoints-->Components
Endpoints-->StaticAssets
MVCRp-->Endpoints
- ResourceAsset and its related types represent Blazor's view of a resource, which only includes things that are useful when including that resource in markup.
- ResourceAssetDescriptor and its related types are the "HTTP" view of the file/endpoint that we mapped.
The Component endpoints assembly is the one responsible for mapping the StaticAssetDescriptors into the ResourceAssetCollection so that Blazor and MVC can consume them.
Microsoft.AspNetCore.Components.dll
namespace Microsoft.AspNetCore.Components;
public abstract class ComponentBase : IComponent, IHandleEvent, IHandleAfterRender
{
+ protected ResourceAssetCollection Assets { get; }
}
public readonly struct RenderHandle
{
+ public ResourceAssetCollection Assets { get; }
}
public abstract partial class Renderer : IDisposable, IAsyncDisposable
{
+ protected internal virtual ResourceAssetCollection Assets { get; } = ResourceAssetCollection.Empty;
}
+public class ResourceAssetCollection : IReadOnlyList<ResourceAsset>
+{
+ public static readonly ResourceAssetCollection Empty = new([]);
+ public ResourceAssetCollection(IReadOnlyList<ResourceAsset> resources);
+ public string this[string key];
+ public bool IsContentSpecificUrl(string path);
+}
+public class ResourceAsset(string url, IReadOnlyList<ResourceAssetProperty>? properties)
+{
+ public string Url { get; } = url;
+ public IReadOnlyList<ResourceAssetProperty>? Properties { get; } = properties;
+}
+public class ResourceAssetProperty(string name, string value)
+{
+ public string Name { get; } = name;
+ public string Value { get; } = value;
+}Microsoft.AspNetCore.Components.Endpoints
namespace Microsoft.AspNetCore.Components;
+public class ImportMap : IComponent
+{
+ [CascadingParameter] public HttpContext? HttpContext { get; set; } = null;
+ [Parameter] public ImportMapDefinition? ImportMapDefinition { get; set; }
+}
+public class ImportMapDefinition
+{
+ public ImportMapDefinition(
+ IReadOnlyDictionary<string, string>? imports,
+ IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>>? scopes,
+ IReadOnlyDictionary<string, string>? integrity)
+ public static ImportMapDefinition FromResourceCollection(ResourceAssetCollection assets);
+ public static ImportMapDefinition Combine(params ImportMapDefinition[] sources);
+ public IReadOnlyDictionary<string, string>? Imports { get; }
+ public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>>? Scopes { get; }
+ public IReadOnlyDictionary<string, string>? Integrity { get; }
+ public override string ToString();
+}
public static class RazorComponentsEndpointConventionBuilderExtensions
{
+ public static RazorComponentsEndpointConventionBuilder WithResourceCollection(
+ this RazorComponentsEndpointConventionBuilder builder,
+ string? manifestPath = null);
}Assembly Microsoft.AspNetCore.WebUtilities
namespace Microsoft.AspNetCore.WebUtilities;
public class WebEncoders
{
+ Base64UrlEncode(System.ReadOnlySpan<byte> input, System.Span<char> output)
}Assembly Microsoft.AspNetCore.Mvc.RazorPages
namespace Microsoft.AspNetCore.Builder;
+public static class PageActionEndpointConventionBuilderResourceCollectionExtensions
+{
+ static WithResourceCollection(this Microsoft.AspNetCore.Builder.PageActionEndpointConventionBuilder! builder, string? manifestPath = null): Microsoft.AspNetCore.Builder.PageActionEndpointConventionBuilder!
+}Microsoft.AspNetCore.Mvc.ViewFeatures
namespace Microsoft.AspNetCore.Builder;
+public static class ControllerActionEndpointConventionBuilderResourceCollectionExtensions
+{
+ static WithResourceCollection(this Microsoft.AspNetCore.Builder.ControllerActionEndpointConventionBuilder! builder, string? manifestPath = null): Microsoft.AspNetCore.Builder.ControllerActionEndpointConventionBuilder!
+}Assembly Microsoft.AspNetCore.Mvc.TagHelpers.dll
namespace Microsoft.AspNetCore.Mvc.TagHelpers;
public class ScriptTagHelper
{
+ public string Type { get; set; }
+ public ImportMapDefinition ImportMap { get; set; }
}Assembly Microsoft.AspNetCore.StaticAssets.dll
namespace Microsoft.AspNetCore.StaticAssets;
+public sealed class StaticAssetDescriptor
+{
+ public required string Route { get; set; }
+ public required string AssetPath { get; set; }
+ public IReadOnlyList<StaticAssetSelector> Selectors { get; set; }
+ public IReadOnlyList<StaticAssetProperty> Properties { get; set; }
+ public IReadOnlyList<StaticAssetResponseHeader> ResponseHeaders { get; set; }
+}
+public sealed class StaticAssetResponseHeader(string name, string value)
+{
+ public string Name { get; } = name;
+ public string Value { get; } = value;
+}
+public sealed class StaticAssetProperty(string name, string value)
+{
+ public string Name { get; } = name;
+ public string Value { get; } = value;
+}
+public sealed class StaticAssetSelector(string name, string value, string quality)
+{
+ public string Name { get; } = name;
+ public string Value { get; } = value;
+ public string Quality { get; } = quality;
+}namespace Microsoft.AspNetCore.StaticAssets.Infrastructure;
public static class StaticAssetsEndpointDataSourceHelper
{
+ public static bool HasStaticAssetsDataSource(IEndpointRouteBuilder builder, string? staticAssetsManifestPath = null);
+ public static IReadOnlyList<StaticAssetDescriptor> ResolveStaticAssetDescriptors(
IEndpointRouteBuilder endpointRouteBuilder,
string? manifestPath)
}