Skip to content

[Pre3] Blazor WASM fingerprinting and environment property #35057

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 16 commits into from
Apr 8, 2025
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
2 changes: 1 addition & 1 deletion aspnetcore/blazor/components/built-in-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ The following built-in Razor components are provided by the Blazor framework. Fo
* [`FocusOnNavigate`](xref:blazor/fundamentals/routing#focus-an-element-on-navigation)
* [`HeadContent`](xref:blazor/components/control-head-content)
* [`HeadOutlet`](xref:blazor/components/control-head-content)
* [`ImportMap`](xref:blazor/fundamentals/static-files#import-maps)
* [`ImportMap`](xref:blazor/fundamentals/static-files#importmap-component)
* [`InputCheckbox`](xref:blazor/forms/input-components)
* [`InputDate`](xref:blazor/forms/input-components)
* [`InputFile`](xref:blazor/file-uploads)
Expand Down
60 changes: 46 additions & 14 deletions aspnetcore/blazor/fundamentals/environments.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,34 +28,58 @@ We recommend the following conventions:

## Set the environment

:::moniker range=">= aspnetcore-8.0"

The environment is set using any of the following approaches:

* Blazor Web App: Use any of the approaches described in <xref:fundamentals/environments> for general ASP.NET Core apps.
* Blazor Web App or standalone Blazor WebAssembly: [Blazor start configuration](#set-the-client-side-environment-via-blazor-startup-configuration)
* Standalone Blazor WebAssembly: [`Blazor-Environment` header](#set-the-client-side-environment-via-header)
* Blazor Web App or standalone Blazor WebAssembly: [Azure App Service](#set-the-environment-for-azure-app-service)
:::moniker range=">= aspnetcore-10.0"

* Blazor Web App or Blazor Server: Use any of the approaches described in <xref:fundamentals/environments> for general ASP.NET Core apps.
* Any Blazor app: [Blazor start configuration](#set-the-client-side-environment-via-blazor-startup-configuration)
* Standalone Blazor WebAssembly: `<WasmApplicationEnvironmentName>` property

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:

```html
<!--Blazor-WebAssembly:{"environmentName":"Development", ...}-->
```

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:

```xml
<WasmApplicationEnvironmentName>Staging</WasmApplicationEnvironmentName>
```

The default environments are `Development` for build and `Production` for publish.

:::moniker-end

:::moniker range=">= aspnetcore-8.0 < aspnetcore-10.0"

* Blazor Web App or Blazor Server: Use any of the approaches described in <xref:fundamentals/environments> for general ASP.NET Core apps.
* Any Blazor app:
* [Blazor start configuration](#set-the-client-side-environment-via-blazor-startup-configuration)
* [Azure App Service](#set-the-environment-for-azure-app-service)
* Blazor WebAssembly: [`Blazor-Environment` header](#set-the-client-side-environment-via-header)

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>).

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.

:::moniker-end

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

The environment is set using any of the following approaches:

* Blazor Server: Use any of the approaches described in <xref:fundamentals/environments> for general ASP.NET Core apps.
* Blazor Server or Blazor WebAssembly: [Blazor start configuration](#set-the-client-side-environment-via-blazor-startup-configuration)
* Blazor Server or Blazor WebAssembly:
* [Blazor start configuration](#set-the-client-side-environment-via-blazor-startup-configuration)
* [Azure App Service](#set-the-environment-for-azure-app-service)
* Blazor WebAssembly: [`Blazor-Environment` header](#set-the-client-side-environment-via-header)
* Blazor Server or Blazor WebAssembly: [Azure App Service](#set-the-environment-for-azure-app-service)

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>).

:::moniker-end
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>).

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.

:::moniker-end

For app's running locally in development, the app defaults to the `Development` environment. Publishing the app defaults the environment to `Production`.

:::moniker range="< aspnetcore-5.0"
Expand Down Expand Up @@ -111,10 +135,14 @@ Standalone Blazor WebAssembly:

**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>.

:::moniker range="< aspnetcore-10.0"

Using the `environment` property overrides the environment set by the [`Blazor-Environment` header](#set-the-client-side-environment-via-header).

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.

:::moniker-end

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();`):

```csharp
Expand All @@ -124,6 +152,8 @@ Console.WriteLine(

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

:::moniker range="< aspnetcore-10.0"

## Set the client-side environment via header

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.
Expand Down Expand Up @@ -191,7 +221,7 @@ For more information:
* [Apache documentation (search the latest release for "`mod_headers`")](https://httpd.apache.org/docs/)
* [Blazor WebAssembly Apache hosting guidance](xref:blazor/host-and-deploy/webassembly#apache)

## Set the environment for Azure App Service
### Set the environment for Azure App Service

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

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.

:::moniker-end

## Read the environment in a Blazor WebAssembly app

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.
Expand Down
60 changes: 57 additions & 3 deletions aspnetcore/blazor/fundamentals/static-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,18 +64,21 @@ Assets are delivered via the <xref:Microsoft.AspNetCore.Components.ComponentBase
<link rel="stylesheet" href="@Assets["BlazorSample.styles.css"]" />
```

## Import maps
## `ImportMap` component

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

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`).
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`).

```razor
<ImportMap />
```

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.

> [!NOTE]
> <xref:Microsoft.AspNetCore.Components.ImportMapDefinition> instances are expensive to create, so we recommended caching them when creating an additional instance.

The following examples demonstrate custom import map definitions and the import maps that they create.

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

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>.

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.

:::moniker-end

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.
:::moniker range=">= aspnetcore-10.0"

## Fingerprint client-side static assets in standalone Blazor WebAssembly apps

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.

The following configuration must be present in the `wwwwoot/index.html` file of a standalone Blazor WebAssembly app to adopt fingerprinting:

```html
<head>
...
<script type="importmap"></script>
...
</head>

<body>
...
<script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
...
</body>

</html>
```

In the project file (`.csproj`), the `<WriteImportMapToHtml>` property is set to `true`:

```xml
<PropertyGroup>
<WriteImportMapToHtml>true</WriteImportMapToHtml>
</PropertyGroup>
```

When resolving imports for JavaScript interop, the import map is used by the browser resolve fingerprinted files.

## Fingerprint client-side static assets in Blazor Web Apps

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["..."]`).

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:

```xml
<ItemGroup>
<StaticWebAssetFingerprintPattern Include="JSModule" Pattern="*.mjs"
Expression="#[.{fingerprint}]!" />
</ItemGroup>
```

When resolving imports for JavaScript interop, the import map is used by the browser resolve fingerprinted files.

:::moniker-end

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

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

`MapStaticAssets`:

Expand Down
70 changes: 70 additions & 0 deletions aspnetcore/release-notes/aspnetcore-10/includes/blazor.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,74 @@ Also make this change in the *Routing* article at Line 1633.

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

### Client-side fingerprinting

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.

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.

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:

```diff
<head>
...
+ <script type="importmap"></script>
</head>

<body>
...
- <script src="_framework/blazor.webassembly.js"></script>
+ <script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
</body>

</html>
```

In the project file (`.csproj`), add the `<WriteImportMapToHtml>` property set to `true`:

```diff
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
+ <WriteImportMapToHtml>true</WriteImportMapToHtml>
</PropertyGroup>
</Project>
```

To fingerprint additional JS modules in standalone Blazor WebAssembly apps, use the `<StaticWebAssetFingerprintPattern>` property in the app's project file (`.csproj`).

In the following example, a fingerprint is added for all developer-supplied `.mjs` files in the app:

```xml
<StaticWebAssetFingerprintPattern Include="JSModule" Pattern="*.mjs"
Expression="#[.{fingerprint}]!" />
```

The files are automatically placed into the import map:

* Automatically for Blazor Web App CSR.
* When opting-into module fingerprinting in standalone Blazor WebAssembly apps per the preceding instructions.

When resolving the import for JavaScript interop, the import map is used by the browser resolve fingerprinted files.

### Set the environment in standalone Blazor WebAssembly apps

The `Properties/launchSettings.json` file is no longer used to control the environment in standalone Blazor WebAssembly apps.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maraf This makes it sound like we made a breaking change here. Is the only impact on how you set the environment for standalone Blazor WebAssembly apps during development? Or is there also impact on existing ASP.NET Core hosted apps?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only standalone mode during development affected


Starting in .NET 10, set the environment with the `<WasmApplicationEnvironmentName>` property in the app's project file (`.csproj`).

The following example sets the app's environment to `Staging`:

```xml
<WasmApplicationEnvironmentName>Staging</WasmApplicationEnvironmentName>
```

The default environments are:

* `Development` for build.
* `Production` for publish.

-->