Skip to content

Commit 3a74a17

Browse files
authored
[Pre3] Blazor WASM fingerprinting and environment property (#35057)
1 parent 9006424 commit 3a74a17

File tree

5 files changed

+175
-19
lines changed

5 files changed

+175
-19
lines changed

aspnetcore/blazor/components/built-in-components.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ The following built-in Razor components are provided by the Blazor framework. Fo
2727
* [`FocusOnNavigate`](xref:blazor/fundamentals/routing#focus-an-element-on-navigation)
2828
* [`HeadContent`](xref:blazor/components/control-head-content)
2929
* [`HeadOutlet`](xref:blazor/components/control-head-content)
30-
* [`ImportMap`](xref:blazor/fundamentals/static-files#import-maps)
30+
* [`ImportMap`](xref:blazor/fundamentals/static-files#importmap-component)
3131
* [`InputCheckbox`](xref:blazor/forms/input-components)
3232
* [`InputDate`](xref:blazor/forms/input-components)
3333
* [`InputFile`](xref:blazor/file-uploads)

aspnetcore/blazor/fundamentals/environments.md

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,34 +28,58 @@ We recommend the following conventions:
2828

2929
## Set the environment
3030

31-
:::moniker range=">= aspnetcore-8.0"
32-
3331
The environment is set using any of the following approaches:
3432

35-
* Blazor Web App: Use any of the approaches described in <xref:fundamentals/environments> for general ASP.NET Core apps.
36-
* Blazor Web App or standalone Blazor WebAssembly: [Blazor start configuration](#set-the-client-side-environment-via-blazor-startup-configuration)
37-
* Standalone Blazor WebAssembly: [`Blazor-Environment` header](#set-the-client-side-environment-via-header)
38-
* Blazor Web App or standalone Blazor WebAssembly: [Azure App Service](#set-the-environment-for-azure-app-service)
33+
:::moniker range=">= aspnetcore-10.0"
34+
35+
* Blazor Web App or Blazor Server: Use any of the approaches described in <xref:fundamentals/environments> for general ASP.NET Core apps.
36+
* Any Blazor app: [Blazor start configuration](#set-the-client-side-environment-via-blazor-startup-configuration)
37+
* Standalone Blazor WebAssembly: `<WasmApplicationEnvironmentName>` property
38+
39+
On the client for a Blazor Web App, the environment is determined from the server via an HTML comment that developers don't interact with:
40+
41+
```html
42+
<!--Blazor-WebAssembly:{"environmentName":"Development", ...}-->
43+
```
44+
45+
For a standalone Blazor WebAssembly app, set the environment with the `<WasmApplicationEnvironmentName>` property in the app's project file (`.csproj`). The following example sets the `Staging` environment:
46+
47+
```xml
48+
<WasmApplicationEnvironmentName>Staging</WasmApplicationEnvironmentName>
49+
```
50+
51+
The default environments are `Development` for build and `Production` for publish.
52+
53+
:::moniker-end
54+
55+
:::moniker range=">= aspnetcore-8.0 < aspnetcore-10.0"
56+
57+
* Blazor Web App or Blazor Server: Use any of the approaches described in <xref:fundamentals/environments> for general ASP.NET Core apps.
58+
* Any Blazor app:
59+
* [Blazor start configuration](#set-the-client-side-environment-via-blazor-startup-configuration)
60+
* [Azure App Service](#set-the-environment-for-azure-app-service)
61+
* Blazor WebAssembly: [`Blazor-Environment` header](#set-the-client-side-environment-via-header)
3962

4063
On the client for a Blazor Web App, the environment is determined from the server via a middleware that communicates the environment to the browser via a header named `Blazor-Environment`. The header sets the environment when the <xref:Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHost> is created in the client-side `Program` file (<xref:Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostBuilder.CreateDefault%2A?displayProperty=nameWithType>).
4164

65+
For a standalone Blazor WebAssembly app running locally, the development server adds the `Blazor-Environment` header with the environment name obtained from the hosting environment. The hosting environment sets the environment from the `ASPNETCORE_ENVIRONMENT` environment variable established by the project's `Properties/launchSettings.json` file. The default value of the environment variable in a project created from the Blazor WebAssembly project template is `Development`. For more information, see the [Set the client-side environment via header](#set-the-client-side-environment-via-header) section.
66+
4267
:::moniker-end
4368

4469
:::moniker range="< aspnetcore-8.0"
4570

46-
The environment is set using any of the following approaches:
47-
4871
* Blazor Server: Use any of the approaches described in <xref:fundamentals/environments> for general ASP.NET Core apps.
49-
* Blazor Server or Blazor WebAssembly: [Blazor start configuration](#set-the-client-side-environment-via-blazor-startup-configuration)
72+
* Blazor Server or Blazor WebAssembly:
73+
* [Blazor start configuration](#set-the-client-side-environment-via-blazor-startup-configuration)
74+
* [Azure App Service](#set-the-environment-for-azure-app-service)
5075
* Blazor WebAssembly: [`Blazor-Environment` header](#set-the-client-side-environment-via-header)
51-
* Blazor Server or Blazor WebAssembly: [Azure App Service](#set-the-environment-for-azure-app-service)
52-
53-
On the client for a Blazor Web App or the client of a hosted Blazor WebAssembly app, the environment is determined from the server via a middleware that communicates the environment to the browser via a header named `Blazor-Environment`. The header sets the environment when the <xref:Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHost> is created in the client-side `Program` file (<xref:Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostBuilder.CreateDefault%2A?displayProperty=nameWithType>).
5476

55-
:::moniker-end
77+
On the client of a hosted Blazor WebAssembly app, the environment is determined from the server via a middleware that communicates the environment to the browser via a header named `Blazor-Environment`. The header sets the environment when the <xref:Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHost> is created in the client-side `Program` file (<xref:Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostBuilder.CreateDefault%2A?displayProperty=nameWithType>).
5678

5779
For a standalone Blazor WebAssembly app running locally, the development server adds the `Blazor-Environment` header with the environment name obtained from the hosting environment. The hosting environment sets the environment from the `ASPNETCORE_ENVIRONMENT` environment variable established by the project's `Properties/launchSettings.json` file. The default value of the environment variable in a project created from the Blazor WebAssembly project template is `Development`. For more information, see the [Set the client-side environment via header](#set-the-client-side-environment-via-header) section.
5880

81+
:::moniker-end
82+
5983
For app's running locally in development, the app defaults to the `Development` environment. Publishing the app defaults the environment to `Production`.
6084

6185
:::moniker range="< aspnetcore-5.0"
@@ -111,10 +135,14 @@ Standalone Blazor WebAssembly:
111135

112136
**In the preceding example, the `{BLAZOR SCRIPT}` placeholder is the Blazor script path and file name.** For the location of the script, see <xref:blazor/project-structure#location-of-the-blazor-script>.
113137

138+
:::moniker range="< aspnetcore-10.0"
139+
114140
Using the `environment` property overrides the environment set by the [`Blazor-Environment` header](#set-the-client-side-environment-via-header).
115141

116142
The preceding approach sets the client's environment without changing the `Blazor-Environment` header's value, nor does it change the server project's console logging of the startup environment for a Blazor Web App that has adopted global Interactive WebAssembly rendering.
117143

144+
:::moniker-end
145+
118146
To log the environment to the console in either a standalone Blazor WebAssembly app or the `.Client` project of a Blazor Web App, place the following C# code in the `Program` file after the <xref:Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHost> is created with <xref:Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostBuilder.CreateDefault%2A?displayProperty=nameWithType> and before the line that builds and runs the project (`await builder.Build().RunAsync();`):
119147

120148
```csharp
@@ -124,6 +152,8 @@ Console.WriteLine(
124152

125153
For more information on Blazor startup, see <xref:blazor/fundamentals/startup>.
126154

155+
:::moniker range="< aspnetcore-10.0"
156+
127157
## Set the client-side environment via header
128158

129159
Blazor WebAssembly apps can set the environment with the `Blazor-Environment` header. Specifically, the response header must be set on the `_framework/blazor.boot.json` file, but there's no harm setting the header on file server responses for other Blazor file requests or the entire Blazor deployment.
@@ -191,7 +221,7 @@ For more information, see the following resources:
191221
* [Apache documentation (search the latest release for "`mod_headers`")](https://httpd.apache.org/docs/)
192222
* <xref:blazor/host-and-deploy/webassembly/apache>
193223

194-
## Set the environment for Azure App Service
224+
### Set the environment for Azure App Service
195225

196226
<!-- UPDATE 10.0 The underlying problem with app settings filename
197227
case sensitivity is tracked for 10.0 by ...
@@ -213,6 +243,8 @@ When the app is loaded in the browser, the response header collection for `blazo
213243

214244
App settings from the `appsettings.{ENVIRONMENT}.json` file are loaded by the app, where the `{ENVIRONMENT}` placeholder is the app's environment. In the preceding example, settings from the `appsettings.Staging.json` file are loaded.
215245

246+
:::moniker-end
247+
216248
## Read the environment in a Blazor WebAssembly app
217249

218250
Obtain the app's environment in a component by injecting <xref:Microsoft.AspNetCore.Components.WebAssembly.Hosting.IWebAssemblyHostEnvironment> and reading the <xref:Microsoft.AspNetCore.Components.WebAssembly.Hosting.IWebAssemblyHostEnvironment.Environment> property.

aspnetcore/blazor/fundamentals/static-files.md

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,18 +64,21 @@ Assets are delivered via the <xref:Microsoft.AspNetCore.Components.ComponentBase
6464
<link rel="stylesheet" href="@Assets["BlazorSample.styles.css"]" />
6565
```
6666

67-
## Import maps
67+
## `ImportMap` component
6868

6969
*This section applies to server-side Blazor apps.*
7070

71-
The Import Map component (<xref:Microsoft.AspNetCore.Components.ImportMap>) represents an import map element (`<script type="importmap"></script>`) that defines the import map for module scripts. The Import Map component is placed in `<head>` content of the root component, typically the `App` component (`Components/App.razor`).
71+
The `ImportMap` component (<xref:Microsoft.AspNetCore.Components.ImportMap>) represents an import map element (`<script type="importmap"></script>`) that defines the import map for module scripts. The Import Map component is placed in `<head>` content of the root component, typically the `App` component (`Components/App.razor`).
7272

7373
```razor
7474
<ImportMap />
7575
```
7676

7777
If a custom <xref:Microsoft.AspNetCore.Components.ImportMapDefinition> isn't assigned to an Import Map component, the import map is generated based on the app's assets.
7878

79+
> [!NOTE]
80+
> <xref:Microsoft.AspNetCore.Components.ImportMapDefinition> instances are expensive to create, so we recommended caching them when creating an additional instance.
81+
7982
The following examples demonstrate custom import map definitions and the import maps that they create.
8083

8184
Basic import map:
@@ -197,9 +200,60 @@ For examples of how to address the policy violation with Subresource Integrity (
197200

198201
Configure Static File Middleware to serve static assets to clients by calling <xref:Microsoft.AspNetCore.Builder.StaticFileExtensions.UseStaticFiles%2A> in the app's request processing pipeline. For more information, see <xref:fundamentals/static-files>.
199202

203+
In releases prior to .NET 8, Blazor framework static files, such as the Blazor script, are served via Static File Middleware. In .NET 8 or later, Blazor framework static files are mapped using endpoint routing, and Static File Middleware is no longer used.
204+
200205
:::moniker-end
201206

202-
In releases prior to .NET 8, Blazor framework static files, such as the Blazor script, are served via Static File Middleware. In .NET 8 or later, Blazor framework static files are mapped using endpoint routing, and Static File Middleware is no longer used.
207+
:::moniker range=">= aspnetcore-10.0"
208+
209+
## Fingerprint client-side static assets in standalone Blazor WebAssembly apps
210+
211+
In standalone Blazor WebAssembly apps during build/publish, the framework overrides placeholders in `index.html` with values computed during build to fingerprint static assets for client-side rendering. A [fingerprint](https://wikipedia.org/wiki/Fingerprint_(computing)) is placed into the `blazor.webassembly.js` script file name, and an import map is generated for other .NET assets.
212+
213+
The following configuration must be present in the `wwwwoot/index.html` file of a standalone Blazor WebAssembly app to adopt fingerprinting:
214+
215+
```html
216+
<head>
217+
...
218+
<script type="importmap"></script>
219+
...
220+
</head>
221+
222+
<body>
223+
...
224+
<script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
225+
...
226+
</body>
227+
228+
</html>
229+
```
230+
231+
In the project file (`.csproj`), the `<WriteImportMapToHtml>` property is set to `true`:
232+
233+
```xml
234+
<PropertyGroup>
235+
<WriteImportMapToHtml>true</WriteImportMapToHtml>
236+
</PropertyGroup>
237+
```
238+
239+
When resolving imports for JavaScript interop, the import map is used by the browser resolve fingerprinted files.
240+
241+
## Fingerprint client-side static assets in Blazor Web Apps
242+
243+
For client-side rendering (CSR) in Blazor Web Apps (Interactive Auto or Interactive WebAssembly render modes), static asset server-side [fingerprinting](https://wikipedia.org/wiki/Fingerprint_(computing)) is enabled by adopting [Map Static Assets routing endpoint conventions (`MapStaticAssets`)](xref:fundamentals/map-static-files), [`ImportMap` component](xref:blazor/fundamentals/static-files#importmap-component), and the <xref:Microsoft.AspNetCore.Components.ComponentBase.Assets?displayProperty=nameWithType> property (`@Assets["..."]`).
244+
245+
To fingerprint additional JavaScript modules for CSR, use the `<StaticWebAssetFingerprintPattern>` item in the app's project file (`.csproj`). In the following example, a fingerprint is added for all developer-supplied `.mjs` files in the app:
246+
247+
```xml
248+
<ItemGroup>
249+
<StaticWebAssetFingerprintPattern Include="JSModule" Pattern="*.mjs"
250+
Expression="#[.{fingerprint}]!" />
251+
</ItemGroup>
252+
```
253+
254+
When resolving imports for JavaScript interop, the import map is used by the browser resolve fingerprinted files.
255+
256+
:::moniker-end
203257

204258
## Summary of static file `<link>` `href` formats
205259

aspnetcore/fundamentals/map-static-files.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Creating performant web apps requires optimizing asset delivery to the browser.
4545
* Use [Caching Middleware](xref:performance/caching/middleware).
4646
* Serve [compressed](/aspnet/core/performance/response-compression) versions of the assets when possible. This optimization doesn't include minification.
4747
* Use a [CDN](/microsoft-365/enterprise/content-delivery-networks?view=o365-worldwide&preserve-view=true) to serve the assets closer to the user.
48-
* [Fingerprinting assets](https://developer.mozilla.org/docs/Glossary/Fingerprinting) to prevent reusing old versions of files.
48+
* [Fingerprinting assets](https://en.wikipedia.org/wiki/Fingerprint_(computing)) to prevent reusing old versions of files.
4949

5050
`MapStaticAssets`:
5151

aspnetcore/release-notes/aspnetcore-10/includes/blazor.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,74 @@ Also make this change in the *Routing* article at Line 1633.
132132
133133
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
134134
135+
### Client-side fingerprinting
136+
137+
Last year, the release of .NET 9 introduced [server-side fingerprinting](https://en.wikipedia.org/wiki/Fingerprint_(computing)) of static assets in Blazor Web Apps with the introduction of [Map Static Assets routing endpoint conventions (`MapStaticAssets`)](xref:fundamentals/map-static-files), the [`ImportMap` component](xref:blazor/fundamentals/static-files#importmap-component), and the <xref:Microsoft.AspNetCore.Components.ComponentBase.Assets?displayProperty=nameWithType> property (`@Assets["..."]`) to resolve fingerprinted JavaScript modules. For .NET 10, you can opt-into client-side fingerprinting of JavaScript modules for standalone Blazor WebAssembly apps.
138+
139+
In standalone Blazor WebAssembly apps during build/publish, the framework overrides placeholders in `index.html` with values computed during build to fingerprint static assets. A fingerprint is placed into the `blazor.webassembly.js` script file name.
140+
141+
The following changes must be made in the `wwwwoot/index.html` file to adopt the fingerprinting feature. The standalone Blazor WebAssembly project template will be updated to include these changes in an upcoming preview release:
142+
143+
```diff
144+
<head>
145+
...
146+
+ <script type="importmap"></script>
147+
</head>
148+
149+
<body>
150+
...
151+
- <script src="_framework/blazor.webassembly.js"></script>
152+
+ <script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
153+
</body>
154+
155+
</html>
156+
```
157+
158+
In the project file (`.csproj`), add the `<WriteImportMapToHtml>` property set to `true`:
159+
160+
```diff
161+
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
162+
163+
<PropertyGroup>
164+
<TargetFramework>net10.0</TargetFramework>
165+
<Nullable>enable</Nullable>
166+
<ImplicitUsings>enable</ImplicitUsings>
167+
+ <WriteImportMapToHtml>true</WriteImportMapToHtml>
168+
</PropertyGroup>
169+
</Project>
170+
```
171+
172+
To fingerprint additional JS modules in standalone Blazor WebAssembly apps, use the `<StaticWebAssetFingerprintPattern>` property in the app's project file (`.csproj`).
173+
174+
In the following example, a fingerprint is added for all developer-supplied `.mjs` files in the app:
175+
176+
```xml
177+
<StaticWebAssetFingerprintPattern Include="JSModule" Pattern="*.mjs"
178+
Expression="#[.{fingerprint}]!" />
179+
```
180+
181+
The files are automatically placed into the import map:
182+
183+
* Automatically for Blazor Web App CSR.
184+
* When opting-into module fingerprinting in standalone Blazor WebAssembly apps per the preceding instructions.
185+
186+
When resolving the import for JavaScript interop, the import map is used by the browser resolve fingerprinted files.
187+
188+
### Set the environment in standalone Blazor WebAssembly apps
189+
190+
The `Properties/launchSettings.json` file is no longer used to control the environment in standalone Blazor WebAssembly apps.
191+
192+
Starting in .NET 10, set the environment with the `<WasmApplicationEnvironmentName>` property in the app's project file (`.csproj`).
193+
194+
The following example sets the app's environment to `Staging`:
195+
196+
```xml
197+
<WasmApplicationEnvironmentName>Staging</WasmApplicationEnvironmentName>
198+
```
199+
200+
The default environments are:
201+
202+
* `Development` for build.
203+
* `Production` for publish.
204+
135205
-->

0 commit comments

Comments
 (0)