Skip to content
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: 0 additions & 2 deletions .github/policies/pullRequestManagement-labelFiles.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,5 @@ configuration:
then:
- requestReview:
reviewer: guardrex
- addLabel:
label: 'blazor/subsvc'
onFailure:
onSuccess:
373 changes: 85 additions & 288 deletions aspnetcore/blazor/blazor-ef-core.md

Large diffs are not rendered by default.

5 changes: 1 addition & 4 deletions aspnetcore/blazor/fundamentals/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ For more information, see the following resources:

## Subset of .NET APIs for Blazor WebAssembly apps

A curated list of specific .NET APIs that are supported on the browser for Blazor WebAssembly isn't available. However, you can manually [search for a list of .NET APIs annotated with `[UnsupportedOSPlatform("browser")]`](https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Runtime/Versioning/PlatformAttributes.cs,34041602e232c616,references) to discover .NET APIs that aren't supported in WebAssembly.
A curated list of specific .NET APIs that are supported on the browser for Blazor WebAssembly isn't available. However, you can manually <!--keep--> [search for a list of .NET APIs annotated with `[UnsupportedOSPlatform("browser")]`](https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Runtime/Versioning/PlatformAttributes.cs,34041602e232c616,references) to discover .NET APIs that aren't supported in WebAssembly.

[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)]

Expand All @@ -186,7 +186,6 @@ Samples apps in the repository:
* Blazor Web App
* Blazor WebAssembly
* Blazor Web App Movies tutorial sample (<xref:blazor/tutorials/movie-database-app/index>)
* Blazor Web App with EF Core (<xref:blazor/blazor-ef-core>)
* Blazor Web App with SignalR (<xref:blazor/tutorials/signalr-blazor>)
* Two Blazor Web Apps and a Blazor WebAssembly app for calling web (server) APIs (<xref:blazor/call-web-api>)
* Blazor Web App with OIDC (BFF and non-BFF patterns) (<xref:blazor/security/blazor-web-app-oidc>)
Expand All @@ -205,7 +204,6 @@ Samples apps in the repository:
* Blazor Web App
* Blazor WebAssembly
* Blazor Web App Movies tutorial sample (<xref:blazor/tutorials/movie-database-app/index>)
* Blazor Web App with EF Core (<xref:blazor/blazor-ef-core>)
* Blazor Web App with SignalR (<xref:blazor/tutorials/signalr-blazor>)
* Two Blazor Web Apps and a Blazor WebAssembly app for calling web (server) APIs (<xref:blazor/call-web-api>)
* Blazor Web App with OIDC (BFF and non-BFF patterns) (<xref:blazor/security/blazor-web-app-oidc>)
Expand All @@ -222,7 +220,6 @@ The sample repo contains two types of samples:

* Snippet sample apps provide the code examples that appear in articles. These apps compile but aren't necessarily runnable apps. These apps are useful for merely obtaining example code that appears in articles.
* Samples apps to accompany Blazor articles compile and run for the following scenarios:
* Blazor Server with EF Core
* Blazor Server and Blazor WebAssembly with SignalR
* Blazor WebAssembly scopes-enabled logging

Expand Down
96 changes: 70 additions & 26 deletions aspnetcore/blazor/host-and-deploy/webassembly.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,18 @@ Loading JS from the `<head>` isn't the best approach for the following reasons:
* JS interop may fail if the script depends on Blazor. We recommend loading scripts using one of the other approaches, not via the `<head>` markup.
* The page may become interactive slower due to the time it takes to parse the JS in the script.

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

In component markup, scripts can be loaded via a [`HeadContent` component](xref:blazor/components/control-head-content) with the usual caveat that the approach slows down page load on the client, which we recommend avoiding. When a script is loaded with a `HeadContent` component in a Blazor Server app, Blazor WebAssembly app, or a Blazor Web App using an interactive render mode (interactive SSR, CSR), navigating away from the component's page removes the `<script>` tag from the rendered `<head>` content but doesn't unload the script's JavaScript code, including event handlers that the script registers, exposed variables, and methods that the script provides. Only Blazor Web Apps using static SSR unload JavaScript code when the user navigates away from the page. Generally, you're better off adding `<script>` tags to the physical `<head>` content, unless you explicitly desire to keep such script references in the components that use them and don't mind that the code isn't unloaded on navigation events.

:::moniker-end

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

In component markup, scripts can be loaded via a [`HeadContent` component](xref:blazor/components/control-head-content) with the usual caveat that the approach slows down page load on the client, which we recommend avoiding. When a script is loaded with a `HeadContent` component, navigating away from the component's page removes the `<script>` tag from the rendered `<head>` content but doesn't unload the script's JavaScript code, including event handlers that the script registers, exposed variables, and methods that the script provides. Generally, you're better off adding `<script>` tags to the physical `<head>` content, unless you explicitly desire to keep such script references in the components that use them and don't mind that the code isn't unloaded on navigation events.

:::moniker-end

## Load a script in `<body>` markup

Place the JavaScript tags (`<script>...</script>`) inside the [closing `</body>` element](xref:blazor/project-structure#location-of-head-and-body-content) after the Blazor script reference:
Expand Down
251 changes: 251 additions & 0 deletions aspnetcore/blazor/security/gdpr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
---
title: EU General Data Protection Regulation (GDPR) support in ASP.NET Core Blazor
author: guardrex
description: Learn how to implement EU General Data Protection Regulation (GDPR) support in Blazor apps.
monikerRange: '>= aspnetcore-6.0'
ms.author: riande
ms.custom: mvc
ms.date: 01/16/2025
uid: blazor/security/gdpr
zone_pivot_groups: blazor-app-models
---
# EU General Data Protection Regulation (GDPR) support in ASP.NET Core Blazor

[!INCLUDE[](~/includes/not-latest-version.md)]

This article explains how to implement support for [EU General Data Protection Regulation (GDPR)](https://ec.europa.eu/info/law/law-topic/data-protection/reform/what-does-general-data-protection-regulation-gdpr-govern_en) requirements.

:::zone pivot="server"

In the `Program` file:

* Add <xref:Microsoft.AspNetCore.Builder.CookiePolicyOptions> configuration to require user consent for non-essential cookies and set the same-site policy to none. For more information, see <xref:security/samesite>.
* Add the default implementation for the <xref:Microsoft.AspNetCore.Http.IHttpContextAccessor> service by calling <xref:Microsoft.Extensions.DependencyInjection.HttpServiceCollectionExtensions.AddHttpContextAccessor%2A>.

```csharp
builder.Services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;

options.MinimumSameSitePolicy = SameSiteMode.None;
});

builder.Services.AddHttpContextAccessor();
```

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

In the `Program` file before the call to <xref:Microsoft.AspNetCore.Builder.RazorComponentsEndpointRouteBuilderExtensions.MapRazorComponents%2A>, add Cookie Policy Middleware by calling <xref:Microsoft.AspNetCore.Builder.CookiePolicyAppBuilderExtensions.UseCookiePolicy%2A>:

:::moniker-end

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

In the `Program` file before the call to <xref:Microsoft.AspNetCore.Builder.ComponentEndpointRouteBuilderExtensions.MapBlazorHub%2A>, add Cookie Policy Middleware by calling <xref:Microsoft.AspNetCore.Builder.CookiePolicyAppBuilderExtensions.UseCookiePolicy%2A>:

:::moniker-end

```csharp
app.UseCookiePolicy();
```

Add the following `CookieConsent` component to handle cookie policy consent.

The component uses a [collocated JavaScript file](xref:blazor/js-interop/javascript-location#load-a-script-from-an-external-javascript-file-js-collocated-with-a-component), named `CookieConsent.razor.js`, to load a module. Confirm or adjust the path to the collocated file in the `OnAfterRenderAsync` method. The following component assumes that the component and its companion JavaScript file are in the `Components` folder of the app.

`CookieConsent.razor`:

```razor
@using Microsoft.AspNetCore.Http.Features
@using Microsoft.AspNetCore.Http
@implements IAsyncDisposable
@inject IHttpContextAccessor Http
@inject IJSRuntime JS

@if (showBanner)
{
<div id="cookieConsent" class="alert alert-info alert-dismissible fade show"
role="alert">
Use this space to summarize your privacy and cookie use policy.
<a href="/privacy">Privacy Policy</a>
<button type="button" @onclick="AcceptPolicy" class="accept-policy close"
data-bs-dismiss="alert" aria-label="Close"
data-cookie-string="@cookieString">
Accept
</button>
</div>
}
@code {
private IJSObjectReference? module;
private ITrackingConsentFeature? consentFeature;
private bool showBanner;
private string? cookieString;

protected override void OnInitialized()
{
consentFeature = Http.HttpContext?.Features.Get<ITrackingConsentFeature>();
showBanner = !consentFeature?.CanTrack ?? false;
cookieString = consentFeature?.CreateConsentCookie();
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./Components/CookieConsent.razor.js");
}
}

private async Task AcceptPolicy()
{
if (module is not null)
{
await module.InvokeVoidAsync("acceptPolicy", cookieString);
showBanner = false;
}
}

async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
try
{
await module.DisposeAsync();
}
catch (JSDisconnectedException)
{
}
}
}
}
```

Add the following [collocated JavaScript file](xref:blazor/js-interop/javascript-location#load-a-script-from-an-external-javascript-file-js-collocated-with-a-component) to maintain the `acceptPolicy` function in a JavaScript module.

`CookieConsent.razor.js`:

```javascript
export function acceptPolicy(cookieString) {
document.cookie = cookieString;
}
```

Within `<main>` Razor markup of the `MainLayout` component (`MainLayout.razor`), add the `CookieConsent` component:

```razor
<CookieConsent />
```

## Customize the cookie consent value

Specify the cookie consent value by assigning a custom string to <xref:Microsoft.AspNetCore.Builder.CookiePolicyOptions.ConsentCookieValue%2A?displayProperty=nameWithType>. The following example changes the default value of "`yes`" to "`true`":

```csharp
options.ConsentCookieValue = "true";
```

:::zone-end

:::zone pivot="webassembly"

In Blazor WebAssembly apps, [local storage](https://developer.mozilla.org/docs/Web/API/Window/localStorage) is a convenient approach for maintaining a user's acceptance of a site's cookie policy. The following approach demonstrates the approach.

If the app doesn't already have a `Shared` folder for shared components, add a `Shared` folder to the app.

Add the namespace for shared components to the `_Imports.razor` file. In the following example, the app's namespace is `BlazorSample`, and the shared folder's namespace is `BlazorSample.Shared`:

```razor
@using BlazorSample.Shared
```

Add the following `CookieConsent` component to handle cookie policy consent.

`Shared/CookieConsent.razor`:

```razor
@implements IAsyncDisposable
@inject IJSRuntime JS

@if (showBanner)
{
<div id="cookieConsent" class="alert alert-info alert-dismissible fade show"
role="alert">
Use this space to summarize your privacy and cookie use policy.
<a href="/privacy">Privacy Policy</a>
<button type="button" @onclick="AcceptPolicy" class="accept-policy close"
data-bs-dismiss="alert" aria-label="Close">
<span aria-hidden="true">Accept</span>
</button>
</div>
}
@code {
private IJSObjectReference? module;
private bool showBanner = false;

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./Shared/CookieConsent.razor.js");
showBanner = !await module.InvokeAsync<bool>("getCookiePolicyAccepted");
StateHasChanged();
}
}

private async Task AcceptPolicy()
{
if (module is not null)
{
await module.InvokeVoidAsync("setCookiePolicyAccepted");
showBanner = false;
}
}

async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
try
{
await module.DisposeAsync();
}
catch (JSDisconnectedException)
{
}
}
}
}
```

Add the following [collocated JavaScript file](xref:blazor/js-interop/javascript-location#load-a-script-from-an-external-javascript-file-js-collocated-with-a-component) to maintain the `setCookiePolicyAccepted` and `getCookiePolicyAccepted` functions in a JavaScript module.

`Shared/CookieConsent.razor.js`:

```javascript
export function getCookiePolicyAccepted() {
const cookiePolicy = localStorage.getItem('CookiePolicyAccepted');
return cookiePolicy === 'yes' ? true : false;
}

export function setCookiePolicyAccepted() {
localStorage.setItem('CookiePolicyAccepted', 'yes');
}
```

In the preceding example, you can change the name of the local storage item and value from "`CookiePolicyAccepted`" and "`yes`" to any preferred values. If you change one or both values, update both functions.

Within `<main>` Razor markup of the `MainLayout` component (`Layout/MainLayout.razor`), add the `CookieConsent` component:

```razor
<CookieConsent />
```

:::zone-end

## Additional resources

* [Microsoft Trust Center: Safeguard individual privacy with cloud services from Microsoft: GDPR](https://www.microsoft.com/trust-center/privacy/gdpr-overview)
* [European Commission: Data protection explained](https://ec.europa.eu/info/law/law-topic/data-protection/reform/what-does-general-data-protection-regulation-gdpr-govern_en)
4 changes: 4 additions & 0 deletions aspnetcore/blazor/security/qrcodes-for-authenticator-apps.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ The `EnableAuthenticator` component can be inspected in reference source:

[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)]

## Failures due to TOTP time skew

TOTP authentication depends on accurate time keeping on the TOTP authenticator app device and the app's host. TOTP tokens are only valid for 30 seconds. If logins are failing due to rejected TOTP codes, confirm accurate time is maintained, preferably synchronized to an accurate NTP service.

## Additional resources

* [Using a different QR code library](xref:security/authentication/identity-enable-qrcodes#using-a-different-qr-code-library)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,10 @@ In the `<Authorized>` content of the `<AuthorizeView>` in `Components/Layout/Nav
</AuthorizeView>
```

## Failures due to TOTP time skew

TOTP authentication depends on accurate time keeping on the TOTP authenticator app device and the app's host. TOTP tokens are only valid for 30 seconds. If logins are failing due to rejected TOTP codes, confirm accurate time is maintained, preferably synchronized to an accurate NTP service.

## Additional resources

* [`nimiq/qr-creator`](https://github.com/nimiq/qr-creator)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The ASP.NET Core Request Delegate Generator (RDG) is a tool that generates reque

[!INCLUDE[](~/fundamentals/aot/includes/aot_preview.md)]

The following list contains the [RDG diagnostics](https://source.dot.net/#Microsoft.AspNetCore.Http.RequestDelegateGenerator/DiagnosticDescriptors.cs,44128aef6daa9b5e) for ASP.NET Core:
The following list contains the <!--keep--> [RDG diagnostics](https://source.dot.net/#Microsoft.AspNetCore.Http.RequestDelegateGenerator/DiagnosticDescriptors.cs,44128aef6daa9b5e) for ASP.NET Core:

<!--
* <xref:fundamentals/aot/request-delegate-generator/diagnostics/rdg001>
Expand Down
4 changes: 2 additions & 2 deletions aspnetcore/fundamentals/error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ Another way to use a lambda is to set the status code based on the exception typ

## IExceptionHandler

[IExceptionHandler](https://source.dot.net/#Microsoft.AspNetCore.Diagnostics/ExceptionHandler/IExceptionHandler.cs,adae2915ad0c6dc5) is an interface that gives the developer a callback for handling known exceptions in a central location.
[IExceptionHandler](/dotnet/api/microsoft.aspnetcore.diagnostics.iexceptionhandler) is an interface that gives the developer a callback for handling known exceptions in a central location.

`IExceptionHandler` implementations are registered by calling [`IServiceCollection.AddExceptionHandler<T>`](https://source.dot.net/#Microsoft.AspNetCore.Diagnostics/ExceptionHandler/ExceptionHandlerServiceCollectionExtensions.cs,e74aac24e3e2cbc9). The lifetime of an `IExceptionHandler` instance is singleton. Multiple implementations can be added, and they're called in the order registered.
`IExceptionHandler` implementations are registered by calling [`IServiceCollection.AddExceptionHandler<T>`](/dotnet/api/microsoft.extensions.dependencyinjection.exceptionhandlerservicecollectionextensions.addexceptionhandler). The lifetime of an `IExceptionHandler` instance is singleton. Multiple implementations can be added, and they're called in the order registered.

If an exception handler handles a request, it can return `true` to stop processing. If an exception isn't handled by any exception handler, then control falls back to the default behavior and options from the middleware. Different metrics and logs are emitted for handled versus unhandled exceptions.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ The following code uses a lambda for exception handling:

## IExceptionHandler

[IExceptionHandler](https://source.dot.net/#Microsoft.AspNetCore.Diagnostics/ExceptionHandler/IExceptionHandler.cs,adae2915ad0c6dc5) is an interface that gives the developer a callback for handling known exceptions in a central location.
<xref:Microsoft.AspNetCore.Diagnostics.IExceptionHandler> is an interface that gives the developer a callback for handling known exceptions in a central location.

`IExceptionHandler` implementations are registered by calling [`IServiceCollection.AddExceptionHandler<T>`](https://source.dot.net/#Microsoft.AspNetCore.Diagnostics/ExceptionHandler/ExceptionHandlerServiceCollectionExtensions.cs,e74aac24e3e2cbc9). The lifetime of an `IExceptionHandler` instance is singleton. Multiple implementations can be added, and they're called in the order registered.
`IExceptionHandler` implementations are registered by calling [`IServiceCollection.AddExceptionHandler<T>`](/dotnet/api/microsoft.extensions.dependencyinjection.exceptionhandlerservicecollectionextensions.addexceptionhandler) [`IServiceCollection.AddExceptionHandler<T>`]. The lifetime of an `IExceptionHandler` instance is singleton. Multiple implementations can be added, and they're called in the order registered.

If an exception handler handles a request, it can return `true` to stop processing. If an exception isn't handled by any exception handler, then control falls back to the default behavior and options from the middleware. Different metrics and logs are emitted for handled versus unhandled exceptions.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Worker">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>BackgroundTasksSample</RootNamespace>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
</ItemGroup>

</Project>
Loading
Loading